Premena FunC na FunCtional s Haskell: Ako Serokell vyhral súťaž telegramového blockchainu

Pravdepodobne ste počuli tento telegram sa chystá spustiť blockchainovú platformu Ton. Ale možno vám unikla správa, že nedávno Telegram vyhlásil súťaž na implementáciu jednej alebo viacerých smart kontraktov pre túto platformu.

Tím Serokell s rozsiahlymi skúsenosťami s vývojom veľkých blockchainových projektov nemohol zostať bokom. Do súťaže sme delegovali piatich zamestnancov a o dva týždne neskôr v nej obsadili prvé miesto pod (ne)skromnou náhodnou prezývkou Sexy chameleón. V tomto článku budem hovoriť o tom, ako to urobili. Dúfame, že si v najbližších desiatich minútach prečítate aspoň zaujímavý príbeh a maximálne v ňom nájdete niečo užitočné, čo môžete uplatniť vo svojej práci.

Začnime však malým kontextom.

Súťaž a jej podmienky

Hlavnými úlohami účastníkov teda bola implementácia jednej alebo viacerých navrhovaných inteligentných zmlúv, ako aj predloženie návrhov na zlepšenie ekosystému TON. Súťaž prebiehala od 24. septembra do 15. októbra a výsledky vyhlásili až 15. novembra. Pomerne dlho, ak vezmeme do úvahy, že za tento čas sa Telegramu podarilo uskutočniť a vyhlásiť výsledky súťaží o dizajn a vývoj aplikácií v C++ na testovanie a hodnotenie kvality VoIP hovorov v telegrame.

Zo zoznamu navrhnutého organizátormi sme vybrali dva smart kontrakty. Pre jeden z nich sme použili nástroje distribuované s TON a druhý bol implementovaný v novom jazyku vyvinutom našimi inžiniermi špeciálne pre TON a zabudovanom do Haskellu.

Výber funkčného programovacieho jazyka nie je náhodný. V našom firemný blog Často hovoríme o tom, prečo si myslíme, že komplexnosť funkcionálnych jazykov je obrovská prehnanosť a prečo ich vo všeobecnosti uprednostňujeme pred objektovo orientovanými. Mimochodom, obsahuje aj originál tohto článku.

Prečo sme sa vôbec rozhodli zúčastniť?

Skrátka preto, lebo našou špecializáciou sú neštandardné a komplexné projekty, ktoré si vyžadujú špeciálne zručnosti a často majú pre IT komunitu vedeckú hodnotu. Silne podporujeme open-source vývoj a angažujeme sa v jeho popularizácii a tiež spolupracujeme s poprednými ruskými univerzitami v oblasti informatiky a matematiky.

Zaujímavé úlohy súťaže a zapojenie sa do nášho milovaného projektu Telegram boli samy o sebe výbornou motiváciou, no ďalším stimulom sa stal výherný fond. 🙂

Výskum blockchainu TON

Pozorne sledujeme nový vývoj v oblasti blockchainu, umelej inteligencie a strojového učenia a snažíme sa nevynechať ani jedno významné vydanie v každej z oblastí, v ktorých pracujeme. Preto v čase, keď súťaž začala, náš tím už poznal nápady z Biely papier TON. Pred začatím práce s TON sme však neanalyzovali technickú dokumentáciu a skutočný zdrojový kód platformy, takže prvý krok bol celkom zrejmý – dôkladné preštudovanie oficiálnej dokumentácie na Online a projektové úložiská.

V čase, keď súťaž začala, bol už kód zverejnený, a tak sme sa v rámci šetrenia času rozhodli vyhľadať príručku alebo zhrnutie, ktoré napísal používateľov. Bohužiaľ to neprinieslo žiadne výsledky – okrem návodu na zostavenie platformy na Ubuntu sme nenašli žiadne ďalšie materiály.

Samotná dokumentácia bola dobre preskúmaná, no v niektorých oblastiach bola ťažko čitateľná. Pomerne často sme sa museli vrátiť k určitým bodom a prejsť od opisov abstraktných myšlienok na vysokej úrovni k detailom implementácie na nízkej úrovni.

Jednoduchšie by bolo, keby špecifikácia vôbec neobsahovala podrobný popis implementácie. Informácie o tom, ako virtuálny stroj reprezentuje svoj zásobník, skôr odvrátia pozornosť vývojárov vytvárajúcich inteligentné zmluvy pre platformu TON, než im pomôžu.

Nix: Skladanie projektu

V Serokell sme veľkými fanúšikmi nix. Zhromažďujeme s ním naše projekty a nasadzujeme ich pomocou NixOpsa nainštalované na všetkých našich serveroch OS Nix. Vďaka tomu sú všetky naše zostavy reprodukovateľné a fungujú na akomkoľvek operačnom systéme, na ktorý je možné Nix nainštalovať.

Začali sme teda tvorením Prekrytie Nix s výrazom pre montáž TON. S jeho pomocou je kompilácia TON čo najjednoduchšia:

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

Upozorňujeme, že nemusíte inštalovať žiadne závislosti. Nix zázračne urobí všetko za vás, či už používate NixOS, Ubuntu alebo macOS.

Programovanie pre TON

Kód inteligentnej zmluvy v sieti TON funguje na virtuálnom stroji TON (TVM). TVM je komplexnejší ako väčšina ostatných virtuálnych strojov a má veľmi zaujímavú funkcionalitu, s ktorou dokáže napríklad pracovať pokračovaniach и odkazy na údaje.

Okrem toho chlapci z TON vytvorili tri nové programovacie jazyky:

Päť je univerzálny zásobníkový programovací jazyk, ktorý sa podobá vpred. Jeho super schopnosťou je schopnosť interakcie s TVM.

FunC je programovací jazyk smart contract, ktorý je podobný C a je skompilovaný do iného jazyka – Fift Assembler.

Piaty zostavovateľ — Knižnica XNUMX na generovanie binárneho spustiteľného kódu pre TVM. Piaty Assembler nemá kompilátor. Toto Jazyk špecifický pre vstavanú doménu (eDSL).

Naša súťaž funguje

Konečne je čas pozrieť sa na výsledky nášho snaženia.

Asynchrónny platobný kanál

Platobný kanál je inteligentná zmluva, ktorá umožňuje dvom používateľom posielať platby mimo blockchainu. V dôsledku toho ušetríte nielen peniaze (bez provízie), ale aj čas (nemusíte čakať na spracovanie ďalšieho bloku). Platby môžu byť také malé, ako si želáte, a tak často, ako sa požaduje. V tomto prípade si strany nemusia navzájom dôverovať, pretože férovosť konečného vyrovnania je zaručená smart kontraktom.

Našli sme pomerne jednoduché riešenie problému. Dve strany si môžu vymieňať podpísané správy, pričom každá obsahuje dve čísla – celú sumu zaplatenú každou stranou. Tieto dve čísla fungujú ako vektorové hodiny v tradičných distribuovaných systémoch a nastaviť pri transakciách poradie „stalo sa predtým“. Pomocou týchto údajov bude zmluva schopná vyriešiť akýkoľvek možný konflikt.

V skutočnosti na implementáciu tejto myšlienky stačí jedno číslo, ale obe sme opustili, pretože týmto spôsobom by sme mohli vytvoriť pohodlnejšie používateľské rozhranie. Okrem toho sme sa rozhodli zahrnúť sumu platby do každej správy. Bez toho, ak sa správa z nejakého dôvodu stratí, potom, hoci všetky sumy a konečný výpočet budú správne, používateľ si stratu nemusí všimnúť.

Aby sme otestovali náš nápad, hľadali sme príklady použitia takéhoto jednoduchého a výstižného protokolu platobného kanála. Prekvapivo sme našli iba dva:

  1. Popis podobný prístup, len v prípade jednosmerného kanála.
  2. Návod, ktorá popisuje rovnakú myšlienku ako tá naša, no bez vysvetlenia mnohých dôležitých detailov, ako je všeobecná korektnosť a postupy riešenia konfliktov.

Ukázalo sa, že má zmysel podrobne opísať náš protokol a venovať osobitnú pozornosť jeho správnosti. Po niekoľkých iteráciách bola špecifikácia hotová a teraz môžete aj vy. pozri na ňu.

Zmluvu sme implementovali vo FunC a napísali sme obslužný program príkazového riadka na interakciu s našou zmluvou úplne vo Fift, ako to odporúčali organizátori. Pre naše CLI sme si mohli vybrať akýkoľvek iný jazyk, ale mali sme záujem vyskúšať aplikáciu Fit a zistiť, ako funguje v praxi.

Úprimne povedané, po spolupráci s Fift sme nevideli žiadne presvedčivé dôvody, prečo uprednostniť tento jazyk pred populárnymi a aktívne používanými jazykmi s vyvinutými nástrojmi a knižnicami. Programovanie v stackovom jazyku je dosť nepríjemné, keďže musíte neustále držať v hlave, čo je na stacku a kompilátor s tým nepomáha.

Preto je podľa nášho názoru jediným odôvodnením existencie Fift jeho úloha ako hostiteľského jazyka pre Fift Assembler. Nebolo by však lepšie vložiť assembler TVM do nejakého existujúceho jazyka, ako vymýšľať nový na tento v podstate jediný účel?

TVM Haskell eDSL

Teraz je čas hovoriť o našej druhej inteligentnej zmluve. Rozhodli sme sa vyvinúť peňaženku s viacerými podpismi, no písať ďalšiu smart zmluvu vo FunC by bolo príliš nudné. Chceli sme pridať nejakú chuť, a to bol náš vlastný montážny jazyk pre TVM.

Rovnako ako Fift Assembler je náš nový jazyk zabudovaný, ale ako hostiteľa sme namiesto Fift vybrali Haskell, čo nám umožňuje naplno využiť jeho systém pokročilého typu. Pri práci so smart kontraktmi, kde môžu byť náklady aj na malú chybu veľmi vysoké, je podľa nášho názoru statické písanie veľkou výhodou.

Aby sme demonštrovali, ako vyzerá TVM assembler vložený do Haskellu, implementovali sme naň štandardnú peňaženku. Tu je niekoľko vecí, ktorým treba venovať pozornosť:

  • Táto zmluva pozostáva z jednej funkcie, ale môžete ich použiť toľko, koľko chcete. Keď definujete novú funkciu v hostiteľskom jazyku (t. j. Haskell), naša eDSL vám umožní vybrať si, či chcete, aby sa stala samostatnou rutinou v TVM alebo jednoducho vloženou v bode hovoru.
  • Podobne ako Haskell aj funkcie majú typy, ktoré sa kontrolujú v čase kompilácie. V našom eDSL je typ vstupu funkcie typ zásobníka, ktorý funkcia očakáva, a typ výsledku je typ zásobníka, ktorý sa vytvorí po volaní.
  • Kód má anotácie stacktype, popisujúci očakávaný typ zásobníka v bode volania. V pôvodnej zmluve o peňaženke to boli len komentáre, ale v našom eDSL sú v skutočnosti súčasťou kódu a kontrolujú sa pri kompilácii. Môžu slúžiť ako dokumentácia alebo vyhlásenia, ktoré pomôžu vývojárovi nájsť problém, ak sa zmení kód a typ zásobníka. Samozrejme, takéto anotácie neovplyvňujú výkon runtime, pretože sa pre ne negeneruje žiadny TVM kód.
  • Toto je stále prototyp napísaný za dva týždne, takže na projekte je ešte veľa práce. Napríklad všetky inštancie tried, ktoré vidíte v kóde nižšie, by sa mali generovať automaticky.

Takto vyzerá implementácia multisig peňaženky na našej 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šej zmluvy o eDSL a viacpodpisovej peňaženke nájdete na toto úložisko. A viac podrobne povedané o vstavaných jazykoch, náš kolega Georgy Agapov.

Závery o súťaži a TON

Spolu nám práca zabrala 380 hodín (vrátane oboznámenia sa s dokumentáciou, stretnutí a samotného vývoja). Súťažného projektu sa zúčastnilo päť vývojárov: CTO, vedúci tímu, špecialisti na blockchain platformu a vývojári softvéru Haskell.

Našli sme zdroje na účasť v súťaži bez problémov, pretože duch hackathonu, úzka tímová práca a potreba rýchlo sa ponoriť do aspektov nových technológií je vždy vzrušujúca. Niekoľko bezsenných nocí na dosiahnutie maximálnych výsledkov v podmienkach obmedzených zdrojov je kompenzovaných neoceniteľnými skúsenosťami a vynikajúcimi spomienkami. Okrem toho je práca na takýchto úlohách vždy dobrou skúškou procesov spoločnosti, pretože je mimoriadne ťažké dosiahnuť skutočne slušné výsledky bez dobre fungujúcej internej interakcie.

Texty bokom: boli sme ohromení množstvom práce, ktorú vynaložil tím TON. Podarilo sa im vybudovať zložitý, krásny a hlavne fungujúci systém. TON sa ukázal ako platforma s veľkým potenciálom. Aby sa však tento ekosystém mohol rozvíjať, je potrebné urobiť oveľa viac, a to ako z hľadiska jeho využitia v blockchain projektoch, tak aj z hľadiska zlepšovania vývojových nástrojov. Sme hrdí, že sme teraz súčasťou tohto procesu.

Ak máte po prečítaní tohto článku stále nejaké otázky alebo nápady, ako použiť TON na riešenie vašich problémov, napíš nám — radi sa podelíme o naše skúsenosti.

Zdroj: hab.com

Pridať komentár