Spreminjanje FunC-a v FunCtional s Haskell-om: Kako je Serokell zmagal na tekmovanju Telegram Blockchain

Verjetno ste že slišali za ta Telegram je tik pred lansiranjem platforme Ton blockchain. Morda pa ste spregledali novico, da je nedolgo nazaj Telegram objavil natečaj za implementacijo ene ali več pametnih pogodb za to platformo.

Ekipa Serokell z bogatimi izkušnjami pri razvoju velikih blockchain projektov ni mogla stati ob strani. Na natečaj smo delegirali pet zaposlenih, ki so dva tedna kasneje na njem zasedli prvo mesto pod (ne)skromnim naključnim vzdevkom Sexy Chameleon. V tem članku bom govoril o tem, kako so to storili. Upamo, da boste v naslednjih desetih minutah prebrali vsaj kakšno zanimivo zgodbo, kvečjemu pa v njej našli kaj koristnega, kar boste lahko uporabili pri svojem delu.

A začnimo z malo konteksta.

Konkurenca in njeni pogoji

Glavne naloge udeležencev so bile torej implementacija ene ali več predlaganih pametnih pogodb ter dajanje predlogov za izboljšanje ekosistema TON. Natečaj je potekal od 24. septembra do 15. oktobra, rezultati pa so bili znani šele 15. novembra. Precej dolgo, glede na to, da je Telegram v tem času uspel izvesti in objaviti rezultate natečajev o oblikovanju in razvoju aplikacij v C++ za testiranje in ocenjevanje kakovosti VoIP klicev v Telegramu.

Iz seznama, ki so ga predlagali organizatorji, smo izbrali dve pametni pogodbi. Za eno od njih smo uporabili orodja, distribuirana s TON, drugo pa smo implementirali v novem jeziku, ki so ga razvili naši inženirji posebej za TON in vgradili v Haskell.

Izbira funkcionalnega programskega jezika ni naključna. V našem korporativni blog Pogosto govorimo o tem, zakaj se nam zdi kompleksnost funkcionalnih jezikov veliko pretiravanje in zakaj jih imamo na splošno raje kot objektno usmerjene. Mimogrede, vsebuje tudi izvirnik tega članka.

Zakaj smo se sploh odločili sodelovati?

Skratka, ker so naša specializacija nestandardni in kompleksni projekti, ki zahtevajo posebna znanja in so pogosto znanstvene vrednosti za IT skupnost. Močno podpiramo razvoj odprte kode in se ukvarjamo z njeno popularizacijo ter sodelujemo z vodilnimi ruskimi univerzami na področju računalništva in matematike.

Zanimive naloge tekmovanja in sodelovanje v našem ljubem projektu Telegram so bile same po sebi odlična motivacija, a nagradni sklad je postal dodatna spodbuda. 🙂

TON raziskava blockchain

Pozorno spremljamo novosti na področju blockchaina, umetne inteligence in strojnega učenja ter se trudimo, da ne bi zamudili niti ene pomembne izdaje na vsakem od področij, na katerih delujemo. Zato je bila naša ekipa do začetka tekmovanja že seznanjena z idejami iz TON belega papirja. Vendar pred začetkom dela s TON nismo analizirali tehnične dokumentacije in dejanske izvorne kode platforme, zato je bil prvi korak povsem očiten - temeljita študija uradne dokumentacije o Online in repozitorije projektov.

Ko se je tekmovanje začelo, je bila koda že objavljena, zato smo se zaradi prihranka časa odločili poiskati vodnik ali povzetek, ki ga je napisal s strani uporabnikov. Na žalost to ni dalo nobenih rezultatov - razen navodil za sestavljanje platforme na Ubuntuju nismo našli drugih materialov.

Sama dokumentacija je bila dobro raziskana, vendar je bila na nekaterih področjih težko berljiva. Pogosto smo se morali vrniti k določenim točkam in preklopiti z visokonivojskih opisov abstraktnih idej na nizkonivojske izvedbene podrobnosti.

Lažje bi bilo, če specifikacija sploh ne bi vsebovala natančnega opisa izvedbe. Informacije o tem, kako virtualni stroj predstavlja svoj sklad, bodo bolj verjetno odvrnile razvijalce, ki ustvarjajo pametne pogodbe za platformo TON, kot da bi jim pomagale.

Nix: sestavljanje projekta

Pri Serokellu smo veliki oboževalci Nix. Z njim zbiramo svoje projekte in jih uporabljamo NixOps, in nameščen na vseh naših strežnikih Nix OS. Zahvaljujoč temu so vse naše gradnje ponovljive in delujejo na katerem koli operacijskem sistemu, na katerega je mogoče namestiti Nix.

Tako smo začeli z ustvarjanjem Prekrivni sloj Nix z izrazom za sklop TON. Z njegovo pomočjo je sestavljanje TON kar se da preprosto:

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

Upoštevajte, da vam ni treba namestiti nobenih odvisnosti. Nix bo čudežno naredil vse namesto vas, ne glede na to, ali uporabljate NixOS, Ubuntu ali macOS.

Programiranje za TON

Koda pametne pogodbe v omrežju TON deluje na virtualnem stroju TON (TVM). TVM je bolj zapleten kot večina drugih virtualnih strojev in ima zelo zanimivo funkcionalnost, na primer lahko deluje nadaljevanja и povezave do podatkov.

Poleg tega so fantje iz TON ustvarili tri nove programske jezike:

Petica je univerzalni skladovni programski jezik, ki je podoben Četrto. Njegova super sposobnost je sposobnost interakcije s TVM.

FunC je programski jezik za pametne pogodbe, ki je podoben C in je preveden v drug jezik - Fift Assembler.

Peti sestavljalec — Knjižnica Fift za generiranje binarne izvedljive kode za TVM. Fifth Assembler nima prevajalnika. to Vdelani domensko specifični jezik (eDSL).

Naša konkurenca deluje

Končno je čas, da pogledamo rezultate naših prizadevanj.

Asinhroni plačilni kanal

Plačilni kanal je pametna pogodba, ki dvema uporabnikoma omogoča pošiljanje plačil izven verige blokov. Posledično prihranite ne le denar (ni provizije), temveč tudi čas (ni vam treba čakati na obdelavo naslednjega bloka). Plačila so lahko tako majhna, kot želite, in tako pogosto, kot je potrebno. V tem primeru strankama ni treba zaupati druga drugi, saj je poštenost končne poravnave zagotovljena s pametno pogodbo.

Našli smo dokaj preprosto rešitev problema. Dve stranki si lahko izmenjata podpisani sporočili, od katerih vsaka vsebuje dve številki – celoten znesek, ki ga plača vsaka stranka. Ti dve številki delujeta kot vektorska ura v tradicionalnih porazdeljenih sistemih in nastavite vrstni red transakcij "zgodilo se je prej". Z uporabo teh podatkov bo pogodba lahko rešila morebiten konflikt.

Pravzaprav je ena številka dovolj za izvedbo te ideje, vendar smo pustili obe, ker bi tako lahko naredili bolj priročen uporabniški vmesnik. Poleg tega smo se odločili, da v vsakem sporočilu vključimo znesek plačila. Brez tega, če se sporočilo iz nekega razloga izgubi, potem, čeprav bodo vsi zneski in končni izračun pravilni, uporabnik morda ne bo opazil izgube.

Da bi preizkusili našo idejo, smo poiskali primere uporabe tako preprostega in jedrnatega protokola plačilnega kanala. Presenetljivo smo našli le dva:

  1. Opis podoben pristop, le za primer enosmernega kanala.
  2. Vadnica, ki opisuje isto idejo kot naša, vendar brez razlage številnih pomembnih podrobnosti, kot so splošna pravilnost in postopki reševanja sporov.

Postalo je jasno, da je smiselno naš protokol podrobno opisati, posebno pozornost pa posvetiti njegovi pravilnosti. Po več ponovitvah je bila specifikacija pripravljena in zdaj lahko tudi vi. Poglej jo.

Pogodbo smo implementirali v FunC, pripomoček ukazne vrstice za interakcijo z našo pogodbo pa smo v celoti napisali v Fiftu, kot so priporočili organizatorji. Lahko bi izbrali kateri koli drug jezik za naš CLI, vendar nas je zanimalo preizkusiti Fit, da bi videli, kako deluje v praksi.

Iskreno povedano, po delu s Fiftom nismo videli prepričljivih razlogov, da bi raje imeli ta jezik kot priljubljene in aktivno uporabljene jezike z razvitimi orodji in knjižnicami. Programiranje v jeziku, ki temelji na skladu, je precej neprijetno, saj morate nenehno imeti v glavi, kaj je na skladu, prevajalnik pa pri tem ne pomaga.

Zato je po našem mnenju edina utemeljitev za obstoj Fifta njegova vloga gostiteljskega jezika za Fift Assembler. Toda ali ne bi bilo bolje vdelati zbirnik TVM v nek obstoječi jezik, namesto da izumite novega za ta v bistvu edini namen?

TVM Haskell eDSL

Zdaj je čas za pogovor o naši drugi pametni pogodbi. Odločili smo se, da bomo razvili denarnico z več podpisi, vendar bi bilo pisanje še ene pametne pogodbe v FunC preveč dolgočasno. Želeli smo dodati nekaj okusa in to je bil naš lastni montažni jezik za TVM.

Tako kot Fift Assembler je tudi naš novi jezik vdelan, vendar smo za gostitelja namesto Fifta izbrali Haskell, kar nam omogoča, da v celoti izkoristimo njegov napredni tipski sistem. Pri delu s pametnimi pogodbami, kjer je lahko strošek že majhne napake zelo visok, je statično tipkanje po našem mnenju velika prednost.

Da bi prikazali, kako izgleda asembler TVM, vdelan v Haskell, smo vanj implementirali standardno denarnico. Tukaj je nekaj stvari, na katere morate biti pozorni:

  • Ta pogodba je sestavljena iz ene funkcije, vendar jih lahko uporabite, kolikor želite. Ko definirate novo funkcijo v gostiteljskem jeziku (tj. Haskell), vam naš eDSL omogoča izbiro, ali želite, da postane ločena rutina v TVM ali preprosto vstavljena na točki klica.
  • Tako kot Haskell imajo tudi funkcije vrste, ki se preverijo med prevajanjem. V našem eDSL je vhodni tip funkcije tip sklada, ki ga funkcija pričakuje, tip rezultata pa je tip sklada, ki bo ustvarjen po klicu.
  • Koda ima opombe stacktype, ki opisuje pričakovano vrsto sklada na klicni točki. V izvirni pogodbi denarnice so bili to samo komentarji, v našem eDSL pa so dejansko del kode in se preverijo med prevajanjem. Služijo lahko kot dokumentacija ali izjave, ki razvijalcu pomagajo najti težavo, če se spremeni koda in vrsta sklada. Seveda takšne opombe ne vplivajo na zmogljivost izvajanja, saj se zanje ne ustvari koda TVM.
  • To je še vedno prototip, napisan v dveh tednih, tako da je na projektu še veliko dela. Na primer, vsi primerki razredov, ki jih vidite v spodnji kodi, morajo biti ustvarjeni samodejno.

Takole izgleda implementacija multisig denarnice 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

Celotno izvorno kodo naše pogodbe eDSL in denarnice z več podpisi lahko najdete na to skladišče. In več podrobno povedal o vgrajenih jezikih naš kolega Georgij Agapov.

Zaključki o tekmovanju in TON

Skupno je naše delo trajalo 380 ur (vključno s seznanitvijo z dokumentacijo, sestanki in dejanskim razvojem). V tekmovalnem projektu je sodelovalo pet razvijalcev: tehnični direktor, vodja ekipe, specialisti za platformo blockchain in razvijalci programske opreme Haskell.

Vire za sodelovanje v tekmovanju smo našli brez težav, saj je duh hackathona, tesno timsko delo in potreba po hitrem vživljanju v vidike novih tehnologij vedno vznemirljivi. Več neprespanih noči za doseganje maksimalnih rezultatov v razmerah omejenih virov nadomestimo z neprecenljivimi izkušnjami in odličnimi spomini. Poleg tega je delo na takšnih nalogah vedno dober preizkus procesov podjetja, saj je izjemno težko doseči resnično spodobne rezultate brez dobro delujoče notranje interakcije.

Besedila na stran: bili smo navdušeni nad količino dela, ki ga je vložila ekipa TON. Uspelo jim je zgraditi kompleksen, lep in kar je najpomembneje delujoč sistem. TON se je izkazal kot platforma z velikim potencialom. Da pa bi se ta ekosistem razvijal, je treba narediti še veliko več, tako v smislu njegove uporabe v blockchain projektih kot v smislu izboljšave razvojnih orodij. Ponosni smo, da smo zdaj del tega procesa.

Če imate po branju tega članka še vedno kakršna koli vprašanja ali ideje o tem, kako uporabiti TON za reševanje svojih težav, pišite nam — z veseljem delimo svoje izkušnje.

Vir: www.habr.com

Dodaj komentar