Transformarea FunC în funcțional cu Haskell: Cum a câștigat Serokell Concursul Telegram Blockchain

Probabil ați auzit acea Telegramă este pe cale să lanseze platforma Ton blockchain. Dar s-ar putea să fi ratat știrea că nu cu mult timp în urmă Telegramă a anunțat un concurs pentru implementarea unuia sau mai multor contracte inteligente pentru această platformă.

Echipa Serokell, cu o vastă experiență în dezvoltarea de proiecte blockchain mari, nu a putut sta deoparte. Am delegat cinci angajați la competiție, iar două săptămâni mai târziu, aceștia au ocupat primul loc sub porecla aleatoare (in)modesta Sexy Chameleon. În acest articol voi vorbi despre cum au făcut-o. Sperăm că în următoarele zece minute vei citi măcar o poveste interesantă și cel mult vei găsi ceva util în ea pe care să-l aplici în munca ta.

Dar să începem cu puțin context.

Concurența și condițiile acesteia

Așadar, principalele sarcini ale participanților au fost implementarea unuia sau mai multor contracte inteligente propuse, precum și formularea de propuneri pentru îmbunătățirea ecosistemului TON. Concursul a avut loc în perioada 24 septembrie – 15 octombrie, iar rezultatele au fost anunțate abia pe 15 noiembrie. Destul de mult timp, avand in vedere ca in acest timp Telegram a reusit sa sustina si sa anunte rezultatele concursurilor de proiectare si dezvoltare de aplicatii in C++ pentru testarea si evaluarea calitatii apelurilor VoIP in Telegram.

Am selectat două contracte inteligente din lista propusă de organizatori. Pentru unul dintre ele, am folosit instrumente distribuite cu TON, iar al doilea a fost implementat într-un nou limbaj dezvoltat de inginerii noștri special pentru TON și integrat în Haskell.

Alegerea unui limbaj de programare funcțional nu este întâmplătoare. În a noastră blog corporativ Adesea vorbim despre motivul pentru care credem că complexitatea limbajelor funcționale este o uriașă exagerare și de ce le preferăm în general celor orientate pe obiecte. Apropo, conține și originalul acestui articol.

De ce ne-am hotărât să participăm?

Pe scurt, pentru că specializarea noastră este proiecte non-standard și complexe care necesită abilități speciale și sunt adesea de valoare științifică pentru comunitatea IT. Susținem cu tărie dezvoltarea open-source și suntem angajați în popularizarea acesteia și, de asemenea, cooperăm cu universități ruse de top în domeniul informaticii și matematicii.

Sarcinile interesante ale competiției și implicarea în îndrăgitul nostru proiect Telegram au fost în sine o motivație excelentă, dar fondul de premii a devenit un stimulent suplimentar. 🙂

Cercetare blockchain TON

Monitorizăm îndeaproape noile evoluții în blockchain, inteligența artificială și învățarea automată și încercăm să nu pierdem o singură versiune semnificativă în fiecare dintre domeniile în care lucrăm. Prin urmare, până la începutul competiției, echipa noastră era deja familiarizată cu ideile de la TON hârtie albă. Cu toate acestea, înainte de a începe lucrul cu TON, nu am analizat documentația tehnică și codul sursă real al platformei, așa că primul pas a fost destul de evident - un studiu amănunțit al documentației oficiale privind On-line și depozite de proiecte.

Până la începutul competiției, codul fusese deja publicat, așa că pentru a economisi timp, am decis să căutăm un ghid sau un rezumat scris de utilizatorii. Din păcate, acest lucru nu a dat niciun rezultat - în afară de instrucțiunile de asamblare a platformei pe Ubuntu, nu am găsit alte materiale.

Documentația în sine a fost bine cercetată, dar a fost dificil de citit în unele zone. Destul de des a trebuit să ne întoarcem la anumite puncte și să trecem de la descrieri de nivel înalt ale ideilor abstracte la detalii de implementare de nivel scăzut.

Ar fi mai ușor dacă specificația nu ar include deloc o descriere detaliată a implementării. Informațiile despre modul în care o mașină virtuală își reprezintă stiva este mai probabil să distragă atenția dezvoltatorilor care creează contracte inteligente pentru platforma TON decât să îi ajute.

Nix: realizarea proiectului

La Serokell suntem mari fani Nix. Colectăm proiectele noastre cu el și le implementăm folosind NixOps, și instalat pe toate serverele noastre Nix OS. Datorită acestui fapt, toate versiunile noastre sunt reproductibile și funcționează pe orice sistem de operare pe care poate fi instalat Nix.

Așa că am început prin a crea Suprapunere Nix cu expresie pentru asamblarea TON. Cu ajutorul lui, compilarea TON este cât se poate de simplă:

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

Rețineți că nu trebuie să instalați nicio dependență. Nix va face totul pentru tine, indiferent dacă folosești NixOS, Ubuntu sau macOS.

Programare pentru TON

Codul de contract inteligent din rețeaua TON rulează pe mașina virtuală TON (TVM). TVM este mai complex decât majoritatea altor mașini virtuale și are o funcționalitate foarte interesantă, de exemplu, poate funcționa cu continuări и link-uri către date.

Mai mult, băieții de la TON au creat trei noi limbaje de programare:

Cinci este un limbaj de programare universal care seamănă cu mai departe. Super-abilitatea lui este abilitatea de a interacționa cu TVM.

FunC este un limbaj de programare smart contract care este similar cu C și este compilat într-o altă limbă - Fift Assembler.

Al cincilea asamblator — Cinci biblioteci pentru generarea de cod binar executabil pentru TVM. Fifth Assembler nu are un compilator. Acest Limbajul specific domeniului încorporat (eDSL).

Concurența noastră funcționează

În sfârșit, este timpul să ne uităm la rezultatele eforturilor noastre.

Canal de plată asincron

Canalul de plată este un contract inteligent care permite doi utilizatori să trimită plăți în afara blockchain-ului. Ca rezultat, economisiți nu numai bani (nu există comision), ci și timp (nu trebuie să așteptați ca următorul bloc să fie procesat). Plățile pot fi cât se dorește și cât de des este necesar. În acest caz, părțile nu trebuie să aibă încredere una în alta, deoarece corectitudinea decontării finale este garantată de contractul inteligent.

Am găsit o soluție destul de simplă la problemă. Două părți pot face schimb de mesaje semnate, fiecare conținând două numere - suma totală plătită de fiecare parte. Aceste două numere funcționează ca ceas vectorial în sistemele tradiționale distribuite și setați ordinea „sa întâmplat înainte” asupra tranzacțiilor. Folosind aceste date, contractul va putea rezolva orice posibil conflict.

De fapt, un număr este suficient pentru a implementa această idee, dar le-am lăsat pe amândouă pentru că astfel am putea face o interfață de utilizator mai convenabilă. În plus, am decis să includem suma plății în fiecare mesaj. Fără el, dacă mesajul este pierdut dintr-un motiv oarecare, atunci, deși toate sumele și calculul final vor fi corecte, utilizatorul poate să nu observe pierderea.

Pentru a ne testa ideea, am căutat exemple de utilizare a unui protocol de plată atât de simplu și concis. În mod surprinzător, am găsit doar două:

  1. Descriere o abordare similară, doar în cazul unui canal unidirecțional.
  2. Tutorial, care descrie aceeași idee ca a noastră, dar fără a explica multe detalii importante, precum corectitudinea generală și procedurile de soluționare a conflictelor.

A devenit clar că are sens să descriem protocolul nostru în detaliu, acordând o atenție deosebită corectitudinii sale. După mai multe iterații, specificația era gata, iar acum poți și tu. uită-te la ea.

Am implementat contractul în FunC și am scris utilitarul de linie de comandă pentru a interacționa cu contractul nostru în întregime în Fift, așa cum este recomandat de organizatori. Am fi putut alege orice altă limbă pentru CLI-ul nostru, dar am fost interesați să încercăm Fit pentru a vedea cum a funcționat în practică.

Sincer să fiu, după ce am lucrat cu Fift, nu am văzut niciun motiv convingător pentru a prefera acest limbaj limbilor populare și utilizate în mod activ, cu instrumente și biblioteci dezvoltate. Programarea într-un limbaj bazat pe stivă este destul de neplăcută, deoarece trebuie să păstrați în mod constant în cap ceea ce este pe stivă, iar compilatorul nu ajută la asta.

Prin urmare, în opinia noastră, singura justificare a existenței Fift este rolul său de limbă gazdă pentru Fift Assembler. Dar nu ar fi mai bine să încorporăm asamblatorul TVM într-un limbaj existent, decât să inventăm unul nou pentru acest singur scop?

TVM Haskell eDSL

Acum este timpul să vorbim despre al doilea contract inteligent. Am decis să dezvoltăm un portofel cu semnături multiple, dar să scriem un alt contract inteligent în FunC ar fi prea plictisitor. Am vrut să adăugăm ceva aromă și acesta a fost propriul nostru limbaj de asamblare pentru TVM.

La fel ca Fift Assembler, noul nostru limbaj este încorporat, dar am ales Haskell ca gazdă în loc de Fift, permițându-ne să profităm din plin de sistemul său de tip avansat. Când lucrați cu contracte inteligente, unde costul chiar și al unei mici erori poate fi foarte mare, tastarea statică, în opinia noastră, este un mare avantaj.

Pentru a demonstra cum arată asamblatorul TVM încorporat în Haskell, am implementat un portofel standard pe acesta. Iată câteva lucruri la care să acordați atenție:

  • Acest contract constă dintr-o singură funcție, dar puteți folosi câte doriți. Când definiți o nouă funcție în limba gazdă (adică Haskell), eDSL-ul nostru vă permite să alegeți dacă doriți să devină o rutină separată în TVM sau pur și simplu integrată la punctul de apel.
  • Ca și Haskell, funcțiile au tipuri care sunt verificate în timpul compilării. În eDSL-ul nostru, tipul de intrare al unei funcții este tipul de stivă pe care funcția îl așteaptă, iar tipul de rezultat este tipul de stivă care va fi produs după apel.
  • Codul are adnotări stacktype, care descrie tipul de stivă așteptat la punctul de apel. În contractul original de portofel, acestea erau doar comentarii, dar în eDSL-ul nostru, acestea fac de fapt parte din cod și sunt verificate în timpul compilării. Ele pot servi ca documentație sau declarații care ajută dezvoltatorul să găsească problema dacă codul se modifică și tipul stivei se modifică. Desigur, astfel de adnotări nu afectează performanța de rulare, deoarece nu este generat niciun cod TVM pentru ele.
  • Acesta este încă un prototip scris în două săptămâni, așa că mai este mult de lucru la proiect. De exemplu, toate instanțele claselor pe care le vedeți în codul de mai jos ar trebui să fie generate automat.

Iată cum arată implementarea unui portofel multisig pe eDSL-ul nostru:

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

Codul sursă complet al contractului nostru eDSL și portofel cu semnături multiple poate fi găsit la acest depozit. Și altele spus în detaliu despre limbile încorporate, colegul nostru Georgy Agapov.

Concluzii despre competiție și TON

În total, munca noastră a durat 380 de ore (inclusiv familiarizarea cu documentația, întâlnirile și dezvoltarea efectivă). La proiectul competiției au participat cinci dezvoltatori: CTO, team leader, specialiști în platforme blockchain și dezvoltatori de software Haskell.

Am găsit resurse pentru a participa la concurs fără dificultate, deoarece spiritul unui hackathon, munca în echipă strânsă și nevoia de a ne cufunda rapid în aspectele noilor tehnologii sunt mereu incitante. Câteva nopți nedormite pentru a obține rezultate maxime în condiții de resurse limitate sunt compensate de experiență neprețuită și amintiri excelente. În plus, lucrul la astfel de sarcini este întotdeauna un bun test al proceselor companiei, deoarece este extrem de dificil să obții rezultate cu adevărat decente fără o interacțiune internă funcțională.

Versuri deoparte: am fost impresionați de cantitatea de muncă depusă de echipa TON. Au reușit să construiască un sistem complex, frumos și, cel mai important, funcțional. TON s-a dovedit a fi o platformă cu un mare potențial. Cu toate acestea, pentru ca acest ecosistem să se dezvolte, trebuie făcut mult mai mult, atât în ​​ceea ce privește utilizarea lui în proiecte blockchain, cât și în ceea ce privește îmbunătățirea instrumentelor de dezvoltare. Suntem mândri că acum facem parte din acest proces.

Dacă după ce ați citit acest articol mai aveți întrebări sau aveți idei despre cum să utilizați TON pentru a vă rezolva problemele, scrie-ne — vom fi bucuroși să împărtășim experiența noastră.

Sursa: www.habr.com

Adauga un comentariu