Pretvaranje FunC-a u FunCtional uz Haskell: Kako je Serokell pobijedio na natjecanju Telegram Blockchain

Vjerojatno ste čuli taj Telegram uskoro će pokrenuti Ton blockchain platformu. Ali možda ste propustili vijest da je ne tako davno Telegram raspisao natječaj za implementaciju jednog ili više pametnih ugovora za ovu platformu.

Serokell tim, s velikim iskustvom u razvoju velikih blockchain projekata, nije mogao stajati po strani. Na natjecanje smo delegirali pet djelatnica koje su dva tjedna kasnije zauzele prvo mjesto pod (ne)skromnim nasumičnim nadimkom Sexy Chameleon. U ovom ću članku govoriti o tome kako su to učinili. Nadamo se da ćete u sljedećih desetak minuta pročitati barem zanimljivu priču, a najviše da ćete u njoj pronaći nešto korisno što ćete moći primijeniti u svom radu.

Ali počnimo s malim kontekstom.

Natjecanje i njegovi uvjeti

Dakle, glavni zadaci sudionika bili su implementacija jednog ili više predloženih pametnih ugovora, kao i davanje prijedloga za poboljšanje TON ekosustava. Natječaj je trajao od 24. rujna do 15. listopada, a rezultati su objavljeni tek 15. studenog. Prilično dugo, s obzirom da je za to vrijeme Telegram uspio održati i objaviti rezultate natječaja za dizajn i razvoj aplikacija u C++ za testiranje i ocjenu kvalitete VoIP poziva u Telegramu.

Odabrali smo dva pametna ugovora s popisa koji su predložili organizatori. Za jedan od njih koristili smo alate distribuirane s TON-om, a drugi je implementiran u novom jeziku koji su razvili naši inženjeri posebno za TON i ugrađen u Haskell.

Izbor funkcionalnog programskog jezika nije slučajan. U našem korporativni blog Često govorimo o tome zašto mislimo da je složenost funkcionalnih jezika veliko pretjerivanje i zašto ih općenito preferiramo od objektno orijentiranih. Usput, također sadrži izvornik ovog članka.

Zašto smo uopće odlučili sudjelovati?

Ukratko, jer su naša specijalizacija nestandardni i složeni projekti koji zahtijevaju posebne vještine i često su od znanstvene vrijednosti za IT zajednicu. Snažno podržavamo razvoj otvorenog koda i bavimo se njegovom popularizacijom, a također surađujemo s vodećim ruskim sveučilištima u području računalnih znanosti i matematike.

Zanimljivi zadaci natjecanja i uključenost u naš dragi Telegram projekt bili su sami po sebi izvrsna motivacija, no nagradni fond postao je dodatni poticaj. 🙂

TON blockchain istraživanje

Pomno pratimo novi razvoj u blockchainu, umjetnoj inteligenciji i strojnom učenju i nastojimo ne propustiti nijedno značajno izdanje u svakom od područja na kojima radimo. Stoga je do početka natjecanja naš tim već bio upoznat s idejama iz TON bijeli papir. Međutim, prije početka rada s TON-om nismo analizirali tehničku dokumentaciju i stvarni izvorni kod platforme, tako da je prvi korak bio sasvim očit - temeljito proučavanje službene dokumentacije o Online i repozitorije projekata.

Do početka natjecanja kod je već bio objavljen, pa smo radi uštede vremena odlučili potražiti vodič ili sažetak koji je napisao od strane korisnika. Nažalost, to nije dalo nikakve rezultate - osim uputa za sastavljanje platforme na Ubuntuu, nismo pronašli nikakve druge materijale.

Sama dokumentacija bila je dobro istražena, ali je u nekim dijelovima bila teška za čitanje. Često smo se morali vraćati na određene točke i prebacivati ​​s opisa apstraktnih ideja na visokoj razini na detalje implementacije niske razine.

Bilo bi lakše da specifikacija uopće ne uključuje detaljan opis implementacije. Informacije o tome kako virtualni stroj predstavlja svoj stog vjerojatnije će omesti programere koji stvaraju pametne ugovore za TON platformu nego što će im pomoći.

Nix: sastavljanje projekta

U Serokellu smo veliki obožavatelji Pazi. Njime prikupljamo svoje projekte i implementiramo ih pomoću NixOps, i instaliran na svim našim poslužiteljima Nix OS. Zahvaljujući tome, sve naše verzije mogu se reproducirati i rade na bilo kojem operativnom sustavu na koji se može instalirati Nix.

Pa smo počeli sa stvaranjem Nix prekrivanje s izrazom za TON sklop. Uz njegovu pomoć, sastavljanje TON-a je što je moguće jednostavnije:

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

Imajte na umu da ne morate instalirati nikakve ovisnosti. Nix će magično učiniti sve za vas, bez obzira koristite li NixOS, Ubuntu ili macOS.

Programiranje za TON

Kod pametnog ugovora u TON mreži radi na TON virtualnom stroju (TVM). TVM je složeniji od većine drugih virtualnih strojeva i ima vrlo zanimljive funkcionalnosti, na primjer, s kojima može raditi nastavci и poveznice na podatke.

Štoviše, dečki iz TON-a kreirali su tri nova programska jezika:

Petica je univerzalni stog programski jezik koji nalikuje Dalje. Njegova super sposobnost je sposobnost interakcije s TVM-om.

FunC je programski jezik za pametne ugovore koji je sličan C i kompajlira se u drugi jezik - Fift Assembler.

Peti asembler — Knjižnica Fift za generiranje binarnog izvršnog koda za TVM. Fifth Assembler nema kompajler. Ovaj Ugrađeni jezik specifičan za domenu (eDSL).

Naša konkurencija radi

Konačno, vrijeme je da pogledamo rezultate naših napora.

Asinkroni kanal plaćanja

Kanal plaćanja je pametni ugovor koji dvama korisnicima omogućuje slanje plaćanja izvan blockchaina. Kao rezultat toga, štedite ne samo novac (nema provizije), već i vrijeme (ne morate čekati da se sljedeći blok obradi). Uplate mogu biti onoliko male koliko želite i onoliko često koliko je potrebno. U tom slučaju strane ne moraju vjerovati jedna drugoj, budući da je pravičnost konačne nagodbe zajamčena pametnim ugovorom.

Pronašli smo prilično jednostavno rješenje problema. Dvije strane mogu razmjenjivati ​​potpisane poruke, od kojih svaka sadrži dva broja—puni iznos koji svaka strana plaća. Ova dva broja rade kao vektorski sat u tradicionalnim distribuiranim sustavima i postaviti redoslijed transakcija "dogodilo se prije". Koristeći te podatke, ugovor će moći riješiti svaki mogući sukob.

Zapravo, jedan broj je dovoljan za realizaciju ove ideje, ali smo ostavili oba jer smo tako mogli napraviti praktičnije korisničko sučelje. Osim toga, odlučili smo u svakoj poruci uključiti iznos uplate. Bez toga, ako se poruka iz nekog razloga izgubi, tada, iako će svi iznosi i konačni izračun biti točni, korisnik možda neće primijetiti gubitak.

Kako bismo testirali našu ideju, tražili smo primjere korištenja tako jednostavnog i sažetog protokola kanala plaćanja. Začudo, pronašli smo samo dva:

  1. Opis sličan pristup, samo za slučaj jednosmjernog kanala.
  2. Tutorial, koji opisuje istu ideju kao i naša, ali bez objašnjenja mnogih važnih detalja, kao što su opća ispravnost i postupci rješavanja sukoba.

Postalo je jasno da ima smisla detaljno opisati naš protokol, obraćajući posebnu pozornost na njegovu ispravnost. Nakon nekoliko ponavljanja, specifikacija je bila spremna, a sada možete i vi. Pogledaj je.

Implementirali smo ugovor u FunC-u, a uslužni program naredbenog retka za interakciju s našim ugovorom napisali smo u potpunosti u Fiftu, prema preporuci organizatora. Mogli smo odabrati bilo koji drugi jezik za naš CLI, ali smo bili zainteresirani isprobati Fit da vidimo kako se ponaša u praksi.

Da budemo iskreni, nakon rada s Fiftom, nismo vidjeli nikakve uvjerljive razloge da preferiramo ovaj jezik u odnosu na popularne i aktivno korištene jezike s razvijenim alatima i bibliotekama. Programiranje u jeziku baziranom na stogu prilično je neugodno, jer morate stalno imati u glavi što je na stogu, a kompajler tu ne pomaže.

Stoga je, po našem mišljenju, jedino opravdanje za postojanje Fifta njegova uloga glavnog jezika za Fift Assembler. Ali zar ne bi bilo bolje ugraditi TVM asembler u neki postojeći jezik, umjesto izmišljanja novog za ovu u osnovi jedinu svrhu?

TVM Haskell eDSL

Sada je vrijeme da razgovaramo o našem drugom pametnom ugovoru. Odlučili smo razviti novčanik s više potpisa, ali pisanje još jednog pametnog ugovora u FunC-u bilo bi previše dosadno. Htjeli smo dodati malo arome, a to je bio naš vlastiti asemblerski jezik za TVM.

Kao i Fift Assembler, naš novi jezik je ugrađen, ali smo odabrali Haskell kao host umjesto Fifta, što nam omogućuje da u potpunosti iskoristimo njegov napredni sustav tipova. Kada radite s pametnim ugovorima, gdje trošak čak i male pogreške može biti vrlo visok, statično tipkanje je, po našem mišljenju, velika prednost.

Da bismo pokazali kako TVM asembler izgleda ugrađen u Haskell, na njega smo implementirali standardni novčanik. Evo nekoliko stvari na koje treba obratiti pozornost:

  • Ovaj ugovor se sastoji od jedne funkcije, ali ih možete koristiti koliko god želite. Kada definirate novu funkciju u glavnom jeziku (tj. Haskell), naš eDSL vam omogućuje da odaberete želite li da postane zasebna rutina u TVM-u ili jednostavno ugrađena u točku poziva.
  • Kao i Haskell, funkcije imaju tipove koji se provjeravaju tijekom kompajliranja. U našem eDSL-u, tip unosa funkcije je tip stoga koji funkcija očekuje, a tip rezultata je tip stoga koji će biti proizveden nakon poziva.
  • Kod ima napomene stacktype, opisujući očekivani tip stoga na pozivnoj točki. U izvornom ugovoru novčanika to su bili samo komentari, ali u našem eDSL-u oni su zapravo dio koda i provjeravaju se tijekom kompajliranja. Oni mogu poslužiti kao dokumentacija ili izjave koje pomažu razvojnom programeru pronaći problem ako se kod promijeni i vrsta stoga. Naravno, takve napomene ne utječu na izvedbu vremena izvođenja jer se za njih ne generira TVM kod.
  • Ovo je još uvijek prototip koji je napisan u dva tjedna, tako da ima još puno posla na projektu. Na primjer, sve instance klasa koje vidite u donjem kodu trebale bi se automatski generirati.

Ovako izgleda implementacija multisig novčanika na našem eDSL-u:

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

Potpuni izvorni kod našeg eDSL i ugovora novčanika s više potpisa može se pronaći na ovo spremište. I više detaljno ispričao o ugrađenim jezicima, naš kolega Georgij Agapov.

Zaključci o natječaju i TON

Ukupno je naš rad trajao 380 sati (uključujući upoznavanje s dokumentacijom, sastanke i sam razvoj). U natjecateljskom projektu sudjelovalo je pet programera: tehnički direktor, voditelj tima, stručnjaci za blockchain platformu i programeri Haskell softvera.

Pronašli smo resurse za sudjelovanje u natjecanju bez poteškoća, budući da je duh hackathona, tijesan timski rad i potreba da brzo uronimo u aspekte novih tehnologija uvijek uzbudljivi. Nekoliko neprospavanih noći za postizanje maksimalnih rezultata u uvjetima ograničenih resursa kompenzira se neprocjenjivim iskustvom i izvrsnim uspomenama. Osim toga, rad na takvim zadacima uvijek je dobar test procesa tvrtke, budući da je iznimno teško postići doista pristojne rezultate bez dobro funkcionirajuće interne interakcije.

Stihove na stranu: bili smo impresionirani količinom rada koju je uložio TON tim. Uspjeli su izgraditi složen, lijep i, što je najvažnije, radni sustav. TON se pokazao kao platforma s velikim potencijalom. No, da bi se ovaj ekosustav razvijao potrebno je još puno toga učiniti, kako u smislu njegove upotrebe u blockchain projektima, tako i u smislu poboljšanja razvojnih alata. Ponosni smo što smo sada dio ovog procesa.

Ako nakon čitanja ovog članka još uvijek imate bilo kakvih pitanja ili ideja o tome kako koristiti TON za rješavanje svojih problema, pišite nam — rado ćemo podijeliti svoje iskustvo.

Izvor: www.habr.com

Dodajte komentar