Přeměna FunC na funkční s Haskell: Jak Serokell vyhrál soutěž telegramového blockchainu

Pravděpodobně jste slyšeli tento telegram chystá spuštění blockchainové platformy Ton. Ale možná vám unikla zpráva, že nedávno Telegram vyhlásila soutěž pro implementaci jedné nebo více smart kontraktů pro tuto platformu.

Tým Serokell s rozsáhlými zkušenostmi s vývojem velkých blockchainových projektů nemohl zůstat stranou. Do soutěže jsme delegovali pět zaměstnanců a o dva týdny později v ní obsadili první místo pod (ne)skromnou náhodnou přezdívkou Sexy chameleon. V tomto článku budu mluvit o tom, jak to udělali. Doufáme, že si v příštích deseti minutách alespoň přečtete zajímavý příběh a maximálně v něm najdete něco užitečného, ​​co můžete uplatnit ve své práci.

Začněme ale trochou kontextu.

Soutěž a její podmínky

Hlavními úkoly účastníků tedy byla implementace jedné nebo více navrhovaných smart kontraktů a také návrhy na zlepšení ekosystému TON. Soutěž probíhala od 24. září do 15. října a výsledky byly vyhlášeny až 15. listopadu. Poměrně dlouho, vezmeme-li v úvahu, že se za tuto dobu Telegramu podařilo uskutečnit a vyhlásit výsledky soutěží na návrh a vývoj aplikací v C++ pro testování a hodnocení kvality VoIP hovorů v Telegramu.

Ze seznamu navrženého organizátory jsme vybrali dvě chytré smlouvy. Pro jeden z nich jsme použili nástroje distribuované s TON a druhý byl implementován v novém jazyce vyvinutém našimi inženýry speciálně pro TON a zabudovaném do Haskellu.

Volba funkcionálního programovacího jazyka není náhodná. V našem firemní blog Často mluvíme o tom, proč si myslíme, že složitost funkcionálních jazyků je obrovská nadsázka a proč je obecně preferujeme před objektově orientovanými. Ten mimochodem také obsahuje originál tohoto článku.

Proč jsme se vůbec rozhodli zúčastnit?

Stručně řečeno, protože naší specializací jsou nestandardní a složité projekty, které vyžadují speciální dovednosti a často mají pro IT komunitu vědeckou hodnotu. Silně podporujeme open-source vývoj a věnujeme se jeho popularizaci a také spolupracujeme s předními ruskými univerzitami v oblasti informatiky a matematiky.

Zajímavé úkoly soutěže a zapojení do našeho milovaného projektu Telegram byly samy o sobě výbornou motivací, ale další motivací se stal výherní fond. 🙂

výzkum blockchainu TON

Pečlivě sledujeme nový vývoj v oblasti blockchainu, umělé inteligence a strojového učení a snažíme se nevynechat jediné významné vydání v každé z oblastí, ve kterých pracujeme. Proto v době zahájení soutěže byl náš tým již obeznámen s nápady z bílý papír TON. Před zahájením práce s TON jsme však neanalyzovali technickou dokumentaci a skutečný zdrojový kód platformy, takže první krok byl zcela zřejmý – důkladné prostudování oficiální dokumentace na webové stránky a projektová úložiště.

V době, kdy soutěž začala, byl již kód zveřejněn, a tak jsme se pro úsporu času rozhodli hledat průvodce nebo shrnutí napsané uživatele. Bohužel to nepřineslo žádné výsledky – kromě návodu na sestavení platformy na Ubuntu jsme nenašli žádné další materiály.

Samotná dokumentace byla dobře prozkoumána, ale v některých oblastech byla obtížně čitelná. Poměrně často jsme se museli vracet k určitým bodům a přejít od popisů abstraktních myšlenek na vysoké úrovni k podrobnostem implementace na nízké úrovni.

Jednodušší by bylo, kdyby specifikace podrobný popis implementace vůbec neobsahovala. Informace o tom, jak virtuální stroj představuje svůj zásobník, spíše odvede pozornost vývojářů vytvářejících chytré smlouvy pro platformu TON, než aby jim pomohly.

Nix: Dát projekt dohromady

V Serokell jsme velkými fanoušky Nix. Shromažďujeme s ním naše projekty a nasazujeme je pomocí NixOpsa nainstalované na všech našich serverech OS Nix. Díky tomu jsou všechny naše buildy reprodukovatelné a fungují na jakémkoli operačním systému, na který lze Nix nainstalovat.

Začali jsme tedy tvorbou Překrytí Nix s výrazem pro montáž TON. S jeho pomocí je kompilace TON co nejjednodušší:

$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && make

Všimněte si, že nemusíte instalovat žádné závislosti. Nix magicky udělá vše za vás, ať už používáte NixOS, Ubuntu nebo macOS.

Programování pro TON

Kód inteligentní smlouvy v síti TON běží na virtuálním stroji TON (TVM). TVM je komplexnější než většina ostatních virtuálních strojů a má velmi zajímavou funkcionalitu, s kterou umí například pracovat pokračování и odkazy na data.

Navíc kluci z TON vytvořili tři nové programovací jazyky:

Pět je univerzální zásobníkový programovací jazyk, který se podobá Dále. Jeho super schopnost je schopnost interakce s TVM.

FunC je programovací jazyk smart contract, který je podobný C a je zkompilován do jiného jazyka - Fift Assembler.

Pátý assembler — Knihovna Fift pro generování binárního spustitelného kódu pro TVM. Fifth Assembler nemá kompilátor. Tento Jazyk eDSL (Embedded Domain Specific Language).

Naše soutěž funguje

Konečně je čas podívat se na výsledky našeho snažení.

Asynchronní platební kanál

Platební kanál je chytrá smlouva, která umožňuje dvěma uživatelům posílat platby mimo blockchain. Ve výsledku tak ušetříte nejen peníze (bez provize), ale i čas (nemusíte čekat na zpracování dalšího bloku). Platby mohou být tak malé, jak je požadováno, a tak často, jak je požadováno. V tomto případě si strany nemusí navzájem důvěřovat, protože spravedlivost konečného vypořádání je zaručena smart kontraktem.

Našli jsme poměrně jednoduché řešení problému. Dvě strany si mohou vyměňovat podepsané zprávy, z nichž každá obsahuje dvě čísla – plnou částku zaplacenou každou stranou. Tato dvě čísla fungují jako vektorové hodiny v tradičních distribuovaných systémech a u transakcí nastavte pořadí „stalo se dříve“. Pomocí těchto údajů bude smlouva schopna vyřešit jakýkoli možný konflikt.

Ve skutečnosti k realizaci této myšlenky stačí jedno číslo, ale obě jsme opustili, protože tímto způsobem bychom mohli vytvořit pohodlnější uživatelské rozhraní. Navíc jsme se rozhodli uvádět v každé zprávě částku platby. Bez něj, pokud se zpráva z nějakého důvodu ztratí, pak, ačkoli všechny částky a konečný výpočet budou správné, uživatel nemusí ztrátu zaznamenat.

Abychom otestovali náš nápad, hledali jsme příklady použití takového jednoduchého a stručného protokolu platebního kanálu. Překvapivě jsme našli pouze dva:

  1. popis podobný přístup, pouze pro případ jednosměrného kanálu.
  2. Tutorial, která popisuje stejnou myšlenku jako ta naše, ale bez vysvětlení mnoha důležitých detailů, jako je obecná korektnost a postupy řešení konfliktů.

Ukázalo se, že má smysl podrobně popsat náš protokol a věnovat zvláštní pozornost jeho správnosti. Po několika iteracích byla specifikace připravena a nyní můžete také. podívej na ni.

Smlouvu jsme implementovali ve FunC a podle doporučení organizátorů jsme napsali utilitu příkazového řádku pro interakci s naší smlouvou zcela v Fiftu. Pro naše CLI jsme si mohli vybrat jakýkoli jiný jazyk, ale chtěli jsme vyzkoušet Fit, abychom viděli, jak funguje v praxi.

Abych byl upřímný, po práci s Fift jsme neviděli žádné přesvědčivé důvody, proč preferovat tento jazyk před populárními a aktivně používanými jazyky s vyvinutými nástroji a knihovnami. Programování v jazyce založeném na zásobníku je docela nepříjemné, protože musíte neustále mít v hlavě, co je na zásobníku, a kompilátor s tím nepomáhá.

Proto je podle našeho názoru jediným ospravedlněním existence Fift jeho role jako hostitelského jazyka pro Fift Assembler. Nebylo by ale lepší vložit assembler TVM do nějakého existujícího jazyka, než vymýšlet nový pro tento v podstatě jediný účel?

TVM Haskell eDSL

Nyní je čas mluvit o naší druhé chytré smlouvě. Rozhodli jsme se vyvinout peněženku s více podpisy, ale psát další chytrou smlouvu ve FunC by bylo příliš nudné. Chtěli jsme přidat nějakou chuť, a to byl náš vlastní jazyk montáže pro TVM.

Stejně jako Fift Assembler je náš nový jazyk vestavěný, ale jako hostitele jsme místo Fift vybrali Haskell, což nám umožňuje plně využít jeho systém pokročilého typu. Při práci s chytrými kontrakty, kde náklady i na malou chybu mohou být velmi vysoké, je statické psaní podle nás velkou výhodou.

Abychom demonstrovali, jak vypadá TVM assembler vložený do Haskellu, implementovali jsme na něj standardní peněženku. Zde je několik věcí, kterým je třeba věnovat pozornost:

  • Tato smlouva se skládá z jedné funkce, ale můžete jich použít, kolik chcete. Když definujete novou funkci v hostitelském jazyce (tj. Haskell), naše eDSL vám umožní vybrat si, zda se má stát samostatnou rutinou v TVM, nebo jednoduše vloženou v místě volání.
  • Stejně jako Haskell mají funkce typy, které se kontrolují při kompilaci. V našem eDSL je vstupní typ funkce typ zásobníku, který funkce očekává, a typ výsledku je typ zásobníku, který bude vytvořen po volání.
  • Kód má anotace stacktype, popisující očekávaný typ zásobníku v místě volání. V původní smlouvě o peněžence to byly jen komentáře, ale v našem eDSL jsou ve skutečnosti součástí kódu a kontrolují se při kompilaci. Mohou sloužit jako dokumentace nebo prohlášení, která pomohou vývojáři najít problém, pokud se změní kód a typ zásobníku. Takové anotace samozřejmě neovlivňují běhový výkon, protože pro ně není generován žádný TVM kód.
  • Toto je stále prototyp napsaný za dva týdny, takže na projektu zbývá ještě hodně práce. Například všechny instance tříd, které vidíte v kódu níže, by měly být generovány automaticky.

Takto vypadá implementace multisig peněženky na našem eDSL:

main :: IO ()
main = putText $ pretty $ declProgram procedures methods
  where
    procedures =
      [ ("recv_external", decl recvExternal)
      , ("recv_internal", decl recvInternal)
      ]
    methods =
      [ ("seqno", declMethod getSeqno)
      ]

data Storage = Storage
  { sCnt :: Word32
  , sPubKey :: PublicKey
  }

instance DecodeSlice Storage where
  type DecodeSliceFields Storage = [PublicKey, Word32]
  decodeFromSliceImpl = do
    decodeFromSliceImpl @Word32
    decodeFromSliceImpl @PublicKey

instance EncodeBuilder Storage where
  encodeToBuilder = do
    encodeToBuilder @Word32
    encodeToBuilder @PublicKey

data WalletError
  = SeqNoMismatch
  | SignatureMismatch
  deriving (Eq, Ord, Show, Generic)

instance Exception WalletError

instance Enum WalletError where
  toEnum 33 = SeqNoMismatch
  toEnum 34 = SignatureMismatch
  toEnum _ = error "Uknown MultiSigError id"

  fromEnum SeqNoMismatch = 33
  fromEnum SignatureMismatch = 34

recvInternal :: '[Slice] :-> '[]
recvInternal = drop

recvExternal :: '[Slice] :-> '[]
recvExternal = do
  decodeFromSlice @Signature
  dup
  preloadFromSlice @Word32
  stacktype @[Word32, Slice, Signature]
  -- cnt cs sign

  pushRoot
  decodeFromCell @Storage
  stacktype @[PublicKey, Word32, Word32, Slice, Signature]
  -- pk cnt' cnt cs sign

  xcpu @1 @2
  stacktype @[Word32, Word32, PublicKey, Word32, Slice, Signature]
  -- cnt cnt' pk cnt cs sign

  equalInt >> throwIfNot SeqNoMismatch

  push @2
  sliceHash
  stacktype @[Hash Slice, PublicKey, Word32, Slice, Signature]
  -- hash pk cnt cs sign

  xc2pu @0 @4 @4
  stacktype @[PublicKey, Signature, Hash Slice, Word32, Slice, PublicKey]
  -- pubk sign hash cnt cs pubk

  chkSignU
  stacktype @[Bool, Word32, Slice, PublicKey]
  -- ? cnt cs pubk

  throwIfNot SignatureMismatch
  accept

  swap
  decodeFromSlice @Word32
  nip

  dup
  srefs @Word8

  pushInt 0
  if IsEq
  then ignore
  else do
    decodeFromSlice @Word8
    decodeFromSlice @(Cell MessageObject)
    stacktype @[Slice, Cell MessageObject, Word8, Word32, PublicKey]
    xchg @2
    sendRawMsg
    stacktype @[Slice, Word32, PublicKey]

  endS
  inc

  encodeToCell @Storage
  popRoot

getSeqno :: '[] :-> '[Word32]
getSeqno = do
  pushRoot
  cToS
  preloadFromSlice @Word32

Úplný zdrojový kód naší smlouvy o eDSL a peněžence s více podpisy naleznete na adrese toto úložiště. A více podrobně řečeno o vestavěných jazycích, náš kolega Georgy Agapov.

Závěry o soutěži a TON

Celkem naše práce zabrala 380 hodin (včetně seznámení s dokumentací, jednání a samotného vývoje). Soutěžního projektu se zúčastnilo pět vývojářů: CTO, vedoucí týmu, specialisté na blockchain platformu a vývojáři softwaru Haskell.

Našli jsme zdroje, abychom se mohli bez problémů zúčastnit soutěže, protože duch hackathonu, úzká týmová spolupráce a potřeba rychle se ponořit do aspektů nových technologií je vždy vzrušující. Několik bezesných nocí pro dosažení maximálních výsledků v podmínkách omezených zdrojů je kompenzováno neocenitelnými zkušenostmi a vynikajícími vzpomínkami. Práce na takových úkolech je navíc vždy dobrou zkouškou firemních procesů, protože bez dobře fungující interní interakce je extrémně obtížné dosáhnout skutečně slušných výsledků.

Texty stranou: byli jsme ohromeni množstvím práce, kterou odvedl tým TON. Podařilo se jim vybudovat komplexní, krásný a hlavně fungující systém. TON se osvědčil jako platforma s velkým potenciálem. Aby se však tento ekosystém mohl rozvíjet, je potřeba udělat mnohem více, a to jak z hlediska jeho využití v blockchainových projektech, tak z hlediska zlepšování vývojových nástrojů. Jsme hrdí, že jsme nyní součástí tohoto procesu.

Pokud máte po přečtení tohoto článku stále nějaké otázky nebo nápady, jak používat TON k řešení vašich problémů, napište nám — rádi se podělíme o naše zkušenosti.

Zdroj: www.habr.com

Přidat komentář