FunC pavertimas funkciniu su Haskellu: kaip Serokell laimėjo Telegram Blockchain konkursą

Tikriausiai girdėjote tą „Telegram“. ruošiasi paleisti „Ton blockchain“ platformą. Bet galbūt praleidote naujieną, kurią neseniai gavote „Telegram“. paskelbė konkursą vienos ar kelių išmaniųjų sutarčių šiai platformai įgyvendinimui.

Serokell komanda, turinti didelę patirtį kuriant didelius blockchain projektus, negalėjo likti nuošalyje. Į konkursą delegavome penkis darbuotojus, o po dviejų savaičių jie jame užėmė pirmąją vietą (ne)kukliu atsitiktiniu slapyvardžiu Sexy Chameleon. Šiame straipsnyje kalbėsiu apie tai, kaip jie tai padarė. Tikimės, kad per ateinančias dešimt minučių bent jau perskaitysite įdomią istoriją, o daugiausia joje rasite ką nors naudingo, ką galėsite pritaikyti savo darbe.

Bet pradėkime nuo konteksto.

Konkurencija ir jos sąlygos

Taigi pagrindinės dalyvių užduotys buvo vienos ar kelių siūlomų išmaniųjų sutarčių įgyvendinimas, taip pat pasiūlymų teikimas TON ekosistemai tobulinti. Konkursas vyko rugsėjo 24 – spalio 15 dienomis, o rezultatai buvo paskelbti tik lapkričio 15 dieną. Gana ilgai, turint omenyje, kad per tą laiką „Telegram“ sugebėjo surengti ir paskelbti konkursų dėl programų projektavimo ir kūrimo C++, skirtų „Telegram“ VoIP skambučių kokybei išbandyti ir įvertinti.

Iš organizatorių pasiūlyto sąrašo pasirinkome dvi išmaniąsias sutartis. Vienam iš jų naudojome su TON platinamus įrankius, o antrasis buvo įdiegtas nauja kalba, kurią mūsų inžinieriai sukūrė specialiai TON ir įmontuota į Haskell.

Funkcinė programavimo kalba pasirinkta neatsitiktinai. Mūsų įmonės tinklaraštis Mes dažnai kalbame apie tai, kodėl manome, kad funkcinių kalbų sudėtingumas yra didžiulis perdėtas ir kodėl mes dažniausiai jas renkame, o ne į objektus orientuotas kalbas. Beje, jame taip pat yra šio straipsnio originalas.

Kodėl net nusprendėme dalyvauti?

Trumpai tariant, nes mūsų specializacija yra nestandartiniai ir sudėtingi projektai, reikalaujantys specialių įgūdžių ir dažnai turintys mokslinę vertę IT bendruomenei. Mes tvirtai remiame atvirojo kodo plėtrą ir užsiimame jo populiarinimu, taip pat bendradarbiaujame su pirmaujančiais Rusijos universitetais informatikos ir matematikos srityse.

Įdomios konkurso užduotys ir įsitraukimas į mūsų mylimą Telegram projektą savaime buvo puiki motyvacija, tačiau prizinis fondas tapo papildoma paskata. 🙂

TON blokų grandinės tyrimas

Atidžiai stebime naujus blockchain, dirbtinio intelekto ir mašininio mokymosi pokyčius ir stengiamės nepraleisti nė vieno reikšmingo leidinio kiekvienoje srityje, kurioje dirbame. Todėl prasidėjus konkursui mūsų komanda jau buvo susipažinusi su idėjomis iš TON balto popieriaus. Tačiau prieš pradėdami dirbti su TON neanalizavome techninės dokumentacijos ir tikrojo platformos šaltinio kodo, todėl pirmasis žingsnis buvo gana akivaizdus – nuodugnus oficialios dokumentacijos apie Dabar naršo ir projektų saugyklos.

Prasidėjus konkursui kodas jau buvo paskelbtas, todėl taupydami laiką nusprendėme paieškoti vadovo ar santraukos, kurią parašė vartotojų. Deja, tai nedavė jokių rezultatų – be Ubuntu platformos surinkimo instrukcijų, jokių kitų medžiagų neradome.

Pati dokumentacija buvo gerai ištirta, tačiau kai kuriose srityse buvo sunkiai įskaitoma. Gana dažnai tekdavo grįžti prie tam tikrų taškų ir nuo aukšto lygio abstrakčių idėjų aprašymų pereiti prie žemo lygio įgyvendinimo detalių.

Būtų lengviau, jei specifikacijoje iš viso nebūtų detalaus įgyvendinimo aprašymo. Informacija apie tai, kaip virtuali mašina reprezentuoja savo krūvą, greičiausiai atitrauks kūrėjų, kuriančių išmaniąsias sutartis TON platformai, dėmesį, nei padės jiems.

Nix: projekto sujungimas

Mes Serokell esame dideli gerbėjai nulis. Su juo renkame savo projektus ir diegiame juos naudodami NixOps, ir įdiegta visuose mūsų serveriuose „NixOS“. Dėl šios priežasties visos mūsų versijos yra atkuriamos ir veikia bet kurioje operacinėje sistemoje, kurioje galima įdiegti „Nix“.

Taigi pradėjome nuo kūrimo Nix perdanga su išraiška TON surinkimui. Su jo pagalba TON sudarymas yra kuo paprastesnis:

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

Atminkite, kad jums nereikia įdiegti jokių priklausomybių. „Nix“ stebuklingai padarys viską už jus, nesvarbu, ar naudojate „NixOS“, „Ubuntu“, ar „MacOS“.

Programavimas TON

Išmaniosios sutarties kodas TON tinkle veikia TON virtualioje mašinoje (TVM). TVM yra sudėtingesnė nei dauguma kitų virtualių mašinų ir turi labai įdomių funkcijų, pavyzdžiui, su ja gali dirbti tęsiniai и nuorodos į duomenis.

Be to, vaikinai iš TON sukūrė tris naujas programavimo kalbas:

Penkta yra universali programavimo kalba, kuri panaši į Ketvirtas. Jo puikus sugebėjimas yra gebėjimas bendrauti su TVM.

FunC yra išmanioji sutartinių programavimo kalba, kuri yra panaši į C ir yra sudarytas į kitą kalbą - Fift Assembler.

Penktasis surinkėjas — Fift biblioteka, skirta TVM dvejetainiam vykdomajam kodui generuoti. „Fifth Assembler“ neturi kompiliatoriaus. Tai Įterptoji domeno specifinė kalba (eDSL).

Mūsų konkursas veikia

Galiausiai atėjo laikas pažvelgti į mūsų pastangų rezultatus.

Asinchroninis mokėjimo kanalas

Mokėjimo kanalas yra išmanioji sutartis, leidžianti dviem vartotojams siųsti mokėjimus už blokų grandinės ribų. Dėl to sutaupote ne tik pinigų (nėra komisinių), bet ir laiko (nereikia laukti, kol bus apdorotas kitas blokas). Mokėjimai gali būti tokie maži, kiek pageidaujama, ir taip dažnai, kaip reikia. Tokiu atveju šalys neprivalo pasitikėti viena kita, nes galutinio atsiskaitymo teisingumą garantuoja išmanioji sutartis.

Mes radome gana paprastą problemos sprendimą. Dvi šalys gali keistis pasirašytais pranešimais, kurių kiekvienoje yra du numeriai – visa kiekvienos šalies sumokėta suma. Šie du skaičiai veikia kaip vektorinis laikrodis tradicinėse paskirstytose sistemose ir nustatykite operacijų tvarką „atsitiko anksčiau“. Naudojant šiuos duomenis, sutartis galės išspręsti bet kokį galimą konfliktą.

Tiesą sakant, šiai idėjai įgyvendinti pakanka vieno skaičiaus, tačiau palikome abu, nes taip galėjome padaryti patogesnę vartotojo sąsają. Be to, nusprendėme į kiekvieną pranešimą įtraukti mokėjimo sumą. Be jo, jei pranešimas dėl kokių nors priežasčių būtų prarastas, tada, nors visos sumos ir galutinis skaičiavimas bus teisingi, vartotojas gali nepastebėti praradimo.

Norėdami išbandyti savo idėją, ieškojome tokio paprasto ir glausto mokėjimo kanalo protokolo naudojimo pavyzdžių. Keista, bet radome tik du:

  1. aprašymas panašus požiūris, tik vienkrypčio kanalo atveju.
  2. Pamoka, kuriame aprašoma ta pati idėja kaip ir mūsų, tačiau nepaaiškinta daug svarbių detalių, tokių kaip bendras teisingumas ir konfliktų sprendimo procedūros.

Tapo aišku, kad prasminga išsamiai aprašyti mūsų protokolą, ypatingą dėmesį skiriant jo teisingumui. Po kelių pakartojimų specifikacija buvo parengta, o dabar galite ir jūs. pažiūrėk į ją.

Sutartį įgyvendinome FunC, o komandų eilutės įrankį, skirtą sąveikai su mūsų sutartimi, parašėme tik „Fift“, kaip rekomendavo organizatoriai. Galėjome pasirinkti bet kurią kitą savo CLI kalbą, bet mums buvo įdomu išbandyti „Fit“ ir sužinoti, kaip ji veikia praktiškai.

Tiesą sakant, dirbdami su „Fift“ nematėme jokių įtikinamų priežasčių, kodėl reikėtų teikti pirmenybę šiai kalbai, o ne populiarioms ir aktyviai vartojamoms kalboms su sukurtais įrankiais ir bibliotekomis. Programavimas dėklo kalba yra gana nemalonus, nes jūs turite nuolat laikyti galvoje tai, kas yra ant krūvos, o kompiliatorius to nepadeda.

Todėl, mūsų nuomone, vienintelis Fift egzistavimo pateisinimas yra jos, kaip Fift Assembler priimančiosios kalbos, vaidmuo. Bet ar nebūtų geriau TVM asemblerį įterpti į kokią nors esamą kalbą, o ne sugalvoti naują šiam iš esmės vieninteliam tikslui?

TVM Haskell eDSL

Dabar atėjo laikas kalbėti apie antrąją išmaniąją sutartį. Nusprendėme sukurti kelių parašų piniginę, bet rašyti dar vieną išmaniąją sutartį FunC būtų pernelyg nuobodu. Norėjome pridėti šiek tiek skonio, ir tai buvo mūsų pačių TVM surinkimo kalba.

Kaip ir „Fift Assembler“, mūsų naujoji kalba yra įterpta, bet mes pasirinkome „Haskell“ kaip pagrindinį kompiuterį, o ne „Fift“, todėl galime išnaudoti visas pažangios tipo sistemos galimybes. Dirbant su išmaniosiomis sutartimis, kur net ir nedidelės klaidos kaina gali būti labai didelė, statinis spausdinimas, mūsų nuomone, yra didelis privalumas.

Norėdami parodyti, kaip atrodo TVM surinkėjas, įterptas į Haskell, įdiegėme standartinę piniginę. Štai keletas dalykų, į kuriuos reikia atkreipti dėmesį:

  • Šią sutartį sudaro viena funkcija, tačiau galite naudoti tiek, kiek norite. Kai apibrėžiate naują funkciją pagrindinio kompiuterio kalba (t. y. Haskell), mūsų eDSL leidžia pasirinkti, ar norite, kad ji taptų atskira TVM įprasta, ar tiesiog įtraukta į skambučio tašką.
  • Kaip ir Haskell, funkcijos turi tipus, kurie tikrinami kompiliavimo metu. Mūsų eDSL funkcijos įvesties tipas yra dėklo tipas, kurio funkcija tikisi, o rezultato tipas yra dėklo tipas, kuris bus sukurtas po iškvietimo.
  • Kode yra anotacijų stacktype, aprašantis numatomą dėklo tipą iškvietimo taške. Pradinėje piniginės sutartyje tai buvo tik komentarai, tačiau mūsų eDSL jie iš tikrųjų yra kodo dalis ir tikrinami kompiliavimo metu. Jie gali būti naudojami kaip dokumentai arba teiginiai, padedantys kūrėjui rasti problemą, jei pasikeičia kodas ir dėklo tipas. Žinoma, tokie komentarai neturi įtakos vykdymo laikui, nes joms negeneruojamas TVM kodas.
  • Tai vis dar yra prototipas, parašytas per dvi savaites, todėl prie projekto dar reikia daug nuveikti. Pavyzdžiui, visi toliau pateiktame kode matomų klasių egzemplioriai turėtų būti sugeneruoti automatiškai.

Štai kaip atrodo multisig piniginės įdiegimas mūsų 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

Visą mūsų eDSL ir kelių parašų piniginės sutarties šaltinio kodą galite rasti adresu šią saugyklą. Ir dar smulkiai papasakojo apie integruotas kalbas, mūsų kolega Georgijus Agapovas.

Išvados apie konkursą ir TON

Iš viso mūsų darbas užtruko 380 valandų (įskaitant susipažinimą su dokumentacija, susitikimus ir realią plėtrą). Konkurso projekte dalyvavo penki kūrėjai: CTO, komandos vadovas, blockchain platformos specialistai ir Haskell programinės įrangos kūrėjai.

Dalyvauti konkurse radome išteklių be vargo, nes hakatono dvasia, glaudus komandinis darbas ir poreikis greitai pasinerti į naujų technologijų aspektus visada džiugina. Kelias bemieges naktis siekiant maksimalių rezultatų ribotų išteklių sąlygomis kompensuoja neįkainojama patirtis ir puikūs prisiminimai. Be to, darbas atliekant tokias užduotis visada yra geras įmonės procesų išbandymas, nes be gerai veikiančios vidinės sąveikos labai sunku pasiekti tikrai gerų rezultatų.

Dainos žodžiai: mus sužavėjo TON komandos įdėtas darbas. Jiems pavyko sukurti sudėtingą, gražią ir, svarbiausia, veikiančią sistemą. TON įrodė, kad yra platforma, turinti didelį potencialą. Tačiau norint, kad ši ekosistema vystytųsi, reikia daug daugiau nuveikti tiek dėl jos panaudojimo blockchain projektuose, tiek tobulinant kūrimo priemones. Didžiuojamės, kad dabar esame šio proceso dalis.

Jei perskaitę šį straipsnį vis dar turite klausimų ar turite idėjų, kaip naudoti TON savo problemoms išspręsti, parašyk mums – mielai pasidalinsime savo patirtimi.

Šaltinis: www.habr.com

Добавить комментарий