Pretvaranje FunC-a u FunCtional uz Haskell: Kako je Serokell osvojio Telegram Blockchain takmičenje

Verovatno ste čuli za Telegram uskoro će lansirati Ton blockchain platformu. Ali možda ste propustili vijest da je nedavno Telegram raspisao konkurs za implementaciju jednog ili više pametnih ugovora za ovu platformu.

Tim Serokell, sa velikim iskustvom u razvoju velikih blockchain projekata, nije mogao ostati po strani. Na takmičenje smo delegirali pet zaposlenih, a dvije sedmice kasnije su zauzeli prvo mjesto na njemu pod (ne)skromnim slučajnim nadimkom Sexy Chameleon. U ovom članku ću govoriti o tome kako su to učinili. Nadamo se da ćete u narednih desetak minuta barem pročitati zanimljivu priču, a najviše u njoj pronaći nešto korisno što možete primijeniti u svom radu.

Ali počnimo s malim kontekstom.

Konkurencija i njeni uslovi

Dakle, glavni zadaci učesnika bili su implementacija jednog ili više predloženih pametnih ugovora, kao i davanje prijedloga za poboljšanje TON ekosistema. Konkurs je trajao od 24. septembra do 15. oktobra, a rezultati su objavljeni tek 15. novembra. Dosta dugo, s obzirom da je za to vrijeme Telegram uspio održati i objaviti rezultate konkursa na dizajnu i razvoju aplikacija na C++ za testiranje i procjenu kvaliteta VoIP poziva u Telegramu.

Odabrali smo dva pametna ugovora sa liste koju su predložili organizatori. Za jedan od njih koristili smo alate distribuirane sa TON-om, a drugi je implementiran na 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. Inače, sadrži i original ovog članka.

Zašto smo uopšte odlučili da učestvujemo?

Ukratko, zato što su naša specijalizacija nestandardni i složeni projekti koji zahtijevaju posebne vještine i često su od naučne vrijednosti za IT zajednicu. Snažno podržavamo razvoj otvorenog koda i bavimo se njegovom popularizacijom, a takođe sarađujemo sa vodećim ruskim univerzitetima u oblasti računarstva i matematike.

Zanimljivi zadaci takmičenja i uključenost u naš omiljeni Telegram projekat bili su sami po sebi odlična motivacija, ali je nagradni fond postao dodatni podsticaj. 🙂

TON blockchain istraživanje

Pomno pratimo novi razvoj blokova, umjetne inteligencije i strojnog učenja i trudimo se da ne propustimo nijedno značajno izdanje u svakoj oblasti u kojoj radimo. Dakle, u trenutku kada je takmičenje počelo, naš tim je već bio upoznat sa idejama TON bijelog papira. Međutim, prije početka rada sa TON-om nismo analizirali tehničku dokumentaciju i stvarni izvorni kod platforme, tako da je prvi korak bio sasvim očigledan – temeljno proučavanje službene dokumentacije o site i unutra projektna spremišta.

U vrijeme kada je konkurs počeo, kod je već bio objavljen, pa smo kako bismo uštedjeli vrijeme odlučili potražiti vodič ili sažetak koji je napisao korisnika. Nažalost, to nije dalo nikakve rezultate – osim uputstava za sastavljanje platforme na Ubuntu, nismo pronašli nijedan drugi materijal.

Sama dokumentacija je bila dobro istražena, ali je u nekim područjima bila teška za čitanje. Često smo morali da se vratimo na određene tačke i pređemo sa opisa apstraktnih ideja na visokom nivou na detalje implementacije niskog nivoa.

Bilo bi lakše da specifikacija uopće ne sadrži detaljan opis implementacije. Informacije o tome kako virtuelna mašina predstavlja svoj stog će vjerovatnije odvratiti programere koji kreiraju pametne ugovore za TON platformu nego im pomoći.

Nix: sastavljanje projekta

U Serokellu smo veliki fanovi Niks. Sa njim prikupljamo naše projekte i koristimo ih NixOps, i instaliran na svim našim serverima Nix OS. Zahvaljujući tome, sve naše verzije su ponovljive i rade na bilo kojem operativnom sistemu na kojem se Nix može instalirati.

Tako smo počeli sa stvaranjem Nix prekrivač sa izrazom za TON montažu. Uz njegovu pomoć, kompajliranje TON-a je što 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 zavisnosti. Nix će magično učiniti sve za vas, bez obzira da li koristite NixOS, Ubuntu ili macOS.

Programiranje za TON

Kod pametnog ugovora u TON mreži radi na TON virtuelnoj mašini (TVM). TVM je složeniji od većine drugih virtuelnih mašina i ima veoma interesantnu funkcionalnost, na primer, može da radi sa njim nastavci и veze do podataka.

Štaviše, momci iz TON-a su kreirali tri nova programska jezika:

Peto je univerzalni stek programski jezik koji je sličan Četvrto. Njegova super sposobnost je sposobnost interakcije sa TVM-om.

FunC je programski jezik pametnih ugovora koji je sličan C i kompajliran je na drugi jezik - Fift Assembler.

Peti asembler — Peta biblioteka za generisanje binarnog izvršnog koda za TVM. Peti asembler nema kompajler. Ovo Jezik specifičnog za ugrađenu domenu (eDSL).

Naše takmičenje funkcioniše

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

Asinhroni kanal plaćanja

Kanal plaćanja je pametan ugovor koji omogućava dva korisnika da šalju plaćanja izvan blockchaina. Kao rezultat, štedite ne samo novac (nema provizije), već i vrijeme (ne morate čekati da se sljedeći blok obradi). Uplate mogu biti male po želji i često koliko je potrebno. U ovom slučaju, strane ne moraju vjerovati jedna drugoj, budući da je pravičnost konačnog poravnanja zagarantovana 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 funkcionišu kao vektorski sat u tradicionalnim distribuiranim sistemima i postaviti redosled transakcija „pre se desilo“. Koristeći ove podatke, ugovor će moći riješiti svaki mogući sukob.

Zapravo, jedan broj je dovoljan za realizaciju ove ideje, ali smo oboje ostavili jer bismo na taj način mogli napraviti praktičniji korisnički interfejs. Osim toga, odlučili smo da unesemo iznos uplate u svaku poruku. Bez toga, ako se poruka iz nekog razloga izgubi, tada, iako će svi iznosi i konačni obračun biti tačni, korisnik možda neće primijetiti gubitak.

Da bismo testirali našu ideju, potražili smo primjere korištenja tako jednostavnog i sažetog protokola kanala plaćanja. Iznenađujuće, 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, poput opće ispravnosti i postupaka rješavanja sukoba.

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

Ugovor smo implementirali u FunC-u, a uslužni program komandne linije za interakciju sa našim ugovorom u potpunosti smo napisali u Fiftu, prema preporuci organizatora. Mogli smo izabrati bilo koji drugi jezik za naš CLI, ali smo bili zainteresovani da isprobamo Fift da vidimo kako se on ponaša u praksi.

Da budem iskren, nakon rada sa Fiftom, nismo vidjeli nikakve uvjerljive razloge da preferiramo ovaj jezik od popularnih i aktivno korištenih jezika s razvijenim alatima i bibliotekama. Programiranje u jeziku baziranom na steku je prilično neugodno, jer morate stalno držati u glavi ono što je na steku, a kompajler u tome ne pomaže.

Stoga, po našem mišljenju, jedino opravdanje za postojanje Fifta je njegova uloga jezika domaćina za Fift Assembler. Ali zar ne bi bilo bolje ugraditi TVM asembler u neki postojeći jezik, nego izmisliti novi za ovu suštinski jedinu svrhu?

TVM Haskell eDSL

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

Kao i Fift Assembler, naš novi jezik je ugrađen, ali smo izabrali Haskell kao host umjesto Fifta, što nam omogućava da u potpunosti iskoristimo njegov napredni sistem tipova. Kod rada sa pametnim ugovorima, gdje cijena čak i male greške može biti vrlo visoka, statično kucanje je, po našem mišljenju, velika prednost.

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

  • Ovaj ugovor se sastoji od jedne funkcije, ali možete koristiti koliko god želite. Kada definišete novu funkciju u jeziku domaćina (tj. Haskell), naš eDSL vam omogućava da odaberete da li želite da postane posebna rutina u TVM-u ili jednostavno umetnuta na mjestu poziva.
  • Kao i Haskell, funkcije imaju tipove koji se provjeravaju u vrijeme kompajliranja. U našem eDSL-u, tip unosa funkcije je tip steka koji funkcija očekuje, a tip rezultata je tip steka koji će biti proizveden nakon poziva.
  • Kod ima napomene stacktype, koji opisuje očekivani tip steka na pozivnoj točki. U originalnom ugovoru o novčaniku to su bili samo komentari, ali u našem eDSL-u oni su zapravo dio koda i provjeravaju se u vrijeme kompajliranja. Oni mogu poslužiti kao dokumentacija ili izjave koje pomažu programeru da pronađe problem ako se promijeni kod i promijeni tip steka. Naravno, takve napomene ne utiču na performanse vremena izvođenja, jer se za njih ne generiše TVM kod.
  • Ovo je još uvijek prototip napisan za dvije sedmice, tako da ima još puno posla na projektu. Na primjer, sve instance klasa koje vidite u kodu ispod trebale bi biti generirane automatski.

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 ugovora o eDSL-u i novčaniku s više potpisa možete pronaći na ovo spremište. I više detaljno ispričano o ugrađenim jezicima, naš kolega Georgij Agapov.

Zaključci o konkurenciji i TON

Ukupno, naš rad je trajao 380 sati (uključujući upoznavanje sa dokumentacijom, sastanke i stvarni razvoj). U natjecateljskom projektu učestvovalo je pet programera: CTO, vođa tima, stručnjaci za blockchain platformu i programeri Haskell softvera.

Pronašli smo resurse za učešće u takmičenju bez poteškoća, jer je duh hakatona, bliski timski rad i potreba da se brzo uronimo u aspekte novih tehnologija uvek uzbudljiv. Nekoliko neprospavanih noći za postizanje maksimalnih rezultata u uslovima ograničenih resursa nadoknađuje se neprocenjivim iskustvom i odličnim uspomenama. Osim toga, rad na ovakvim zadacima uvijek je dobar test procesa u kompaniji, jer je izuzetno teško postići zaista pristojne rezultate bez dobro funkcioniranja interne komunikacije.

Tekst na stranu: bili smo impresionirani količinom posla koji je uložio TON tim. Uspjeli su izgraditi složen, lijep i što je najvažnije, radni sistem. TON se pokazao kao platforma sa velikim potencijalom. Međutim, da bi se ovaj ekosistem razvio, potrebno je još mnogo 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 i dalje imate pitanja ili imate ideje kako koristiti TON za rješavanje vaših problema, pišite nam — rado ćemo podijeliti svoje iskustvo.

izvor: www.habr.com

Dodajte komentar