Pretvarjanje FunC v FunCtional s pomočjo Haskell: kako je Serokell zmagal na Telegram Blockchain Competition

Verjetno ste slišali, da Telegram pripravljajo zagon blockchain platforme Ton.Morda pa ste spregledali novico, da je Telegram napovedal natečaj za izvedbo enega ali več pametnih pogodb za to platformo.

Ekipa Serokell z bogatimi izkušnjami pri razvoju velikih blockchain projektov ni mogla ostati pasivna. Na natečaj smo delegirali pet svojih sodelavcev, ki so že po dveh tednih zasedli prvo mesto pod (ne)skromnim naključnim vzdevkom Sexy Chameleon. V tem članku bom opisal, kako so to dosegli. Upamo, da boste v naslednjih desetih minutah prebrali zanimivo zgodbo, ali pa boste našli kaj koristnega, kar lahko uporabite v svojem delu.

Ampak začnimo z majhnim potopom v kontekst.

Natečaj in njegove pogoje

Torej, osnovne naloge udeležencev so bile izvedba ene ali več predlaganih pametnih pogodb ter predlogi za izboljšanje ekosistema TON. Natečaj je potekal od 24. septembra do 15. oktobra, rezultati pa so bili objavljeni šele 15. novembra. Kar precej časa, glede na to, da je Telegram v tem času uspešno izvedel in objavil rezultate natečajev za oblikovanje in razvoj aplikacij v C++ za testiranje in oceno kakovosti VoIP klicev v Telegramu.

Iz seznama, ki so ga predlagali organizatorji, smo izbrali dve pametni pogodbi. Za eno smo uporabili orodja, ki so na voljo skupaj s TON, drugo pa smo izvedli v novem jeziku, ki so ga razvili naši inženirji posebej za TON in je bil vgrajen v Haskell.

Izbor funkcionalnega programskega jezika ni naključen. V našem korporativnem blogu pogosto razlagamo, zakaj menimo, da je kompleksnost funkcionalnih jezikov močno prenapihnjena in zakaj na splošno dajemo prednost tem pred objektno usmerjenimi. Mimogrede, v njem je tudi izvirnik tega članka..

Zakaj smo se sploh odločili sodelovati?

Na kratko, zato, ker je naša specializacija izvajanje nestandardnih in zahtevnih projektov, ki zahtevajo posebne veščine ter pogosto predstavljajo znanstveno vrednost za IT skupnost. Močno podpiramo open-source razvoj in se trudimo za njegovo popularizacijo ter sodelujemo z vodilnimi univerzami v Rusiji na področju računalništva in matematike.

Zanimivi izzivi natečaja in povezava z našim ljubim projektom Telegram so bili odlična motivacija, nagradni sklad pa dodatna spodbuda. 🙂

Raziskovanje blockchaina TON

Skrbno spremljamo nove razvojne trende v blockchainu, umetni inteligenci in strojno učenje ter se trudimo, da ne spustimo nobene pomembne izdaje na vsakem področju, s katerim se ukvarjamo. Tako je bila naša ekipa do trenutka začetka natečaja že seznanjena z idejami iz TON bele knjige. Pred začetkom dela s TON pa nismo analizirali tehnične dokumentacije in dejanskega izvornega kode platforme, zato je bil prvi korak dovolj očiten — temeljita raziskava uradne dokumentacije na website in v projektnem repozitoriju.

Ob začetku natečaja je bil koda že objavljena, zato smo za prihranek časa odločili, da poiščemo navodilo ali povzetek, ki ga je napisalo uporabniki. Na žalost to ni prineslo rezultatov — razen navodila za sestavo platforme na Ubuntu nismo našli drugih materialov.

Dokumentacija se je izkazala za skrbno obdelano, a branje le-te je bilo v nekaterih delih težavno. Pogosto smo se morali vračati k različnim točkam in prehajati z višjih opisov abstraktnih idej na nizke podrobnosti implementacije.

Bilo bi lažje, če v specifikaciji sploh ne bi bilo podrobnega opisa implementacije. Informacije o tem, kako virtualni stroj predstavlja svoj sklad, bolj odvračajo razvijalce, ki ustvarjajo pametne pogodbe za platformo TON, kot pa jim pomagajo.

Nix: sestavljamo projekt

V Serokell smo veliki oboževalci Nix. Naše projekte gradimo in implementiramo z uporabo NixOps, vsi naši strežniki pa imajo nameščen NixOS. Tako so vsi naši gradnje reproducibilni in delujejo pod katerim koli operacijskim sistemom, ki podpira Nix.

Zato smo začeli s kreiranjem Nix overlaya z izrazom za gradnjo TON. S tem je sestavljanje TON kar enostavno:

$ 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 vse naredil namesto vas, ne glede na to, ali uporabljate NixOS, Ubuntu ali macOS.

Programiranje za TON

Кoda pametnih pogodbi u TON mreži izvršava se na TON virtuelnoj mašini (TVM). TVM je složenija od većine drugih virtuelnih mašina i ima veoma zanimljivih funkcionalnosti, na primer, može raditi sa nastavcima (continuations) in linkovima na podatke.

Štaviše, tim iz TON-a je stvorio čak tri nova programska jezika:

Fift — univerzalni stekovni programski jezik, koji podseća na Forth. Njegova supermoć je mogućnost interakcije sa TVM.

FunC — programski jezik pametnih ugovora, koji je sličan C i kompajlira se u još jedan jezik — Fift Assembler.

Fift Assembler — Fift biblioteka za generisanje binarnog izvršnog koda za TVM. Fift Assembler ne sadrži kompajler. To je ugrađeni domen-specifični jezik (eDSL).

Naši takmičarski radovi

Napokon, došlo je vreme da pogledamo rezultate naših napora.

Asinhroni platni kanal

Platni kanal (payment channel) — pametni ugovor koji omogućava dva korisnika da šalju uplate izvan blokčejna. Na taj način se štede ne samo novac (nema naknade), već i vreme (ne morate čekati da se obradi sledeća blokada). Uplate mogu biti koliko god male i dešavati se onoliko često koliko je potrebno. Pritom stranama nije potrebno da poveravaju jedna drugoj, jer je pravednost konačnog obračuna garantovana pametnim ugovorom.

Pronašli smo prilično jednostavno rešenje za problem. Dve strane mogu razmenjivati potpisane poruke, od kojih svaka sadrži dva broja — ukupnu sumu koju je platio svaki od učesnika. Ova dva broja funkcionišu kao vektorski sati u tradicionalnim distribuiranim sistemima i određuju redosled ‘desilo se pre’ na transakcijama. Koristeći te podatke, ugovor može rešiti svaki mogući konflikt.

U stvari, za implementaciju ove ideje dovoljno je i jedan broj, ali smo ostavili oba, jer smo tako mogli napraviti praktičniji korisnički interfejs. Pored toga, odlučili smo da uključimo u svaku poruku veličinu uplate. Bez nje, ako poruka iz nekog razloga izgubi, iako su sve sume i konačni obračun ispravni, korisnik može ne primetiti gubitak.

Da bi preizkusili našo idejo, smo poiskali primere uporabe takega enostavnega in jedrnatega protokola plačilnega kanala. Na naše presenečenje smo našli le dva:

  1. Opis podobnega pristopa, le za enosmerni kanal.
  2. Navodila, v katerih je opisana ista ideja kot pri nas, vendar brez razlage mnogih pomembnih podrobnosti, kot sta splošna pravilnost in postopek reševanja konfliktov.

Postalo je jasno, da je smiselno podrobno opisati naš protokol, pri čemer je treba posebno pozornost nameniti njegovi pravilnosti. Po več iteracijah je bila specifikacija pripravljena, zdaj pa jo lahko tudi vi pogledate.

Uresničili smo pogodbo v FunC, orodje ukazne vrstice za interakcijo z našo pogodbo pa smo povsem napisali v Fift, kot so priporočili organizatorji. Izbrati bi lahko kateri koli drug jezik za naš CLI, vendar nas je zanimalo, kako se bo Fift obnesel v praksi.

Iskreno povedano, ko smo delali s Fift, nismo videli tehtnih razlogov, da bi raje izbrali ta jezik pred priljubljenimi in pogosto uporabljenimi jeziki z razvitimi orodji in knjižnicami. Programiranje v jezikih na steklu je precej neprijetno, saj je treba nenehno slediti, kaj in kje je v skladu, kar kompilator ne olajša.

Zato je edini, po našem mnenju, opravičljiv razlog za obstoj Fift njegova vloga kot jezik za Fift Assembler. Ali ne bi bilo bolje vgraditi assembler TVM v že obstoječ jezik, namesto da bi izumili novega za to, v bistvu edino, namen?

TVM Haskell eDSL

Zdaj je čas, da govorimo o našem drugem pametnem pogodbi. Odločili smo se razviti denarnico z več podpisom, vendar bi bilo pisati še eno pametno pogodbo v FunC preveč dolgočasno. Želeli smo dodati neki pečat, in ta pečat je postal naš lasten jezik assemblerja za TVM.

Kot Fift Assembler je naš novi jezik vgrajen, le da smo kot gostitelja namesto Fift izbrali Haskell, kar nam je omogočilo, da v celoti izkoristimo njegov napreden sistem tipov. Pri delu s pametnimi pogodbami, kjer je cena vsake majhne napake lahko zelo visoka, se nam zdi, da je statična tipizacija velika prednost.

Da bi pokazali kako izgleda TVM asembler ugrađen u Haskell, implementirali smo standardni novčanik. Evo nekoliko stvari na koje treba obratiti pažnju:

  • Ova kontrakt se sastoji od jedne funkcije, ali možete koristiti koliko god želite. Kada definišete novu funkciju na jeziku domaćina (tj. Haskellu), naš eDSL vam omogućava da odlučite želite li da postane posebna podprogram u TVM-u ili da bude jednostavno ugrađena na mesto poziva.
  • Kao i u Haskellu, funkcije imaju tipove koji se proveravaju tokom kompajliranja. U našem eDSL, tip ulaza funkcije to je tip steka koji funkcija očekuje, a tip rezultata to je tip steka koji će se dobiti nakon poziva.
  • Kod ima anotacije stacktype, koje opisuju očekivani tip steka na mestu poziva. U originalnom novčaniku, to su bili samo komentari, ali u našem eDSL su zapravo deo koda i proveravaju se tokom kompajliranja. Mogu poslužiti kao dokumentacija ili izjave koje pomažu programeru da otkrije problem u slučaju da se tip steka promeni prilikom izmene koda. Naravno, takve anotacije ne utiču na performanse tokom izvršavanja, pošto se nikakvo TVM kod za njih ne generiše.
  • Ovo je još uvek prototip napisan za dve nedelje, tako da još mnogo posla predstoji. Na primer, svi primeri klasa koje vidite u donjem kodu trebaju biti automatski generisani.

Evo kako izgleda implementacija multisig novčanika u 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

Celoten izvorna koda našega eDSL in pogodbe denarnice z več podpisi lahko najdete v tem repozitoriju. Podrobneje o vgrajenih jezikih je naš kolega Georgij Agapov govoril. Zaključki o natečaju in TON

V skupnem je naše delo trajalo 380 ur (vključno z seznanjanjem z dokumentacijo, sestanki in samo razvijanjem). V natečajnem projektu je sodelovalo pet razvijalcev: CTO, timski vodja, strokovnjaki za blockchain platforme in razvijalci programske opreme v Haskellu.

V skupnem je naše delo trajalo 380 ur (vključno z spoznavanjem dokumentacije, sestanki in samo razvojem). V projektnem natečaju je sodelovalo pet razvijalcev: CTO, vodja ekipe, strokovnjaki za blockchain platforme in razvijalci programske opreme v Haskellu.

Zasoby do udziału w konkursie znaleźliśmy bez trudu, ponieważ duch hackathonu, bliska praca zespołowa oraz potrzeba szybkiego zanurzenia się w aspekty nowych technologii – to zawsze fascynujące. Kilka bezsennych nocy w celu osiągnięcia maksymalnych wyników w warunkach ograniczonych zasobów rekompensuje bezcenne doświadczenie i doskonałe wspomnienia. Ponadto praca nad takimi zadaniami – to zawsze doskonały sprawdzian procesów w firmie, ponieważ osiągnięcie naprawdę satysfakcjonujących wyników bez doskonale zorganizowanej wewnętrznej współpracy jest niezwykle trudne.

Oprócz liryki: byliśmy pod wrażeniem zakresu pracy wykonanego przez zespół TON. Udało im się zbudować skomplikowany, piękny i co najważniejsze, działający system. TON ujawnił się jako platforma o dużym potencjale. Jednak aby ten ekosystem się rozwijał, trzeba zrobić jeszcze bardzo dużo, zarówno w zakresie jego wykorzystania w projektach blockchain, jak i w doskonaleniu narzędzi deweloperskich. Jesteśmy dumni, że teraz jesteśmy częścią tego procesu.

Jeśli po przeczytaniu tego artykułu pozostały Państwu jakieś pytania lub pojawiły się pomysły na to, jak zastosować TON w rozwiązaniu swoich problemów, napiszcie do nas — z przyjemnością podzielimy się doświadczeniem.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster