FunC:n muuttaminen toimivaksi Haskellin kanssa: Kuinka Serokell voitti Telegram Blockchain -kilpailun

Olet varmaan kuullut tuon Telegramin on käynnistämässä Ton-lohkoketjualustan. Mutta olet ehkä missannut uutisen, joka ei kauan sitten Telegram julkaisi kilpailun yhden tai useamman älykkään sopimuksen toteuttamiseksi tälle alustalle.

Serokell-tiimi, jolla on laaja kokemus suurten lohkoketjuprojektien kehittämisestä, ei voinut jäädä sivuun. Delegoimme viisi työntekijää kilpailuun, ja kaksi viikkoa myöhemmin he sijoittuivat siinä (epä)vaatimattomalla satunnaisella lempinimellä Sexy Chameleon. Tässä artikkelissa puhun siitä, kuinka he tekivät sen. Toivomme, että seuraavien kymmenen minuutin aikana luet ainakin mielenkiintoisen tarinan ja löydät siitä korkeintaan jotain hyödyllistä, jota voit soveltaa työssäsi.

Mutta aloitetaan pienestä kontekstista.

Kilpailu ja sen ehdot

Joten osallistujien päätehtävät olivat yhden tai useamman ehdotetun älysopimuksen toteuttaminen sekä ehdotusten tekeminen TON-ekosysteemin parantamiseksi. Kilpailu kesti 24.-15., ja tulokset julkistettiin vasta 15. Melko pitkä aika, kun otetaan huomioon, että tänä aikana Telegram onnistui pitämään ja ilmoittamaan kilpailujen tulokset C++:n sovellusten suunnittelusta ja kehittämisestä VoIP-puheluiden laadun testaamiseksi ja arvioimiseksi Telegramissa.

Valitsimme järjestäjien ehdottamasta listasta kaksi älysopimusta. Yhdessä niistä käytimme TON:n kanssa jaettuja työkaluja, ja toinen toteutettiin uudella kielellä, jonka suunnittelijat olivat kehittäneet erityisesti TON:lle ja sisäänrakennettu Haskellille.

Toimivan ohjelmointikielen valinta ei ole sattumaa. Meidän yrityksen blogi Puhumme usein siitä, miksi mielestämme toiminnallisten kielten monimutkaisuus on valtava liioittelua ja miksi me yleensä pidämme niitä mieluummin kuin oliokieliä. Se muuten sisältää myös tämän artikkelin alkuperäinen.

Miksi edes päätimme osallistua?

Lyhyesti sanottuna, koska erikoisuutemme on epätyypillisiä ja monimutkaisia ​​projekteja, jotka vaativat erityistaitoja ja joilla on usein tieteellistä arvoa IT-yhteisölle. Tuemme vahvasti avoimen lähdekoodin kehitystä ja olemme mukana sen popularisoinnissa, sekä teemme yhteistyötä johtavien venäläisten yliopistojen kanssa tietojenkäsittelytieteen ja matematiikan alalla.

Kilpailun mielenkiintoiset tehtävät ja osallistuminen rakkaaseen Telegram-projektiimme olivat sinänsä erinomainen motivaatio, mutta palkintorahastosta tuli lisäkannustin. 🙂

TON-lohkoketjututkimus

Seuraamme tiiviisti lohkoketjun, tekoälyn ja koneoppimisen uutta kehitystä ja yritämme olla menettämättä yhtäkään merkittävää julkaisua kullakin alalla, jolla työskentelemme. Siksi kilpailun alkaessa tiimimme oli jo tuttu ideoista TON valkoista paperia. Emme kuitenkaan analysoineet alustan teknistä dokumentaatiota ja varsinaista lähdekoodia ennen kuin aloitimme työskennellä TONin kanssa, joten ensimmäinen askel oli varsin ilmeinen - virallisen dokumentaation perusteellinen tutkimus Online ja projektivarastot.

Kilpailun alkaessa koodi oli jo julkaistu, joten ajan säästämiseksi päätimme etsiä oppaan tai yhteenvedon, jonka oli kirjoittanut käyttäjiä. Valitettavasti tämä ei tuottanut tulosta - Ubuntun alustan kokoamisohjeita lukuun ottamatta emme löytäneet muita materiaaleja.

Itse dokumentaatio oli hyvin tutkittua, mutta se oli paikoin vaikea lukea. Usein jouduimme palaamaan tiettyihin kohtiin ja siirtymään abstraktien ideoiden korkean tason kuvauksista matalan tason toteutusyksityiskohtiin.

Helpompaa olisi, jos spesifikaatiossa ei olisi lainkaan yksityiskohtaista kuvausta toteutuksesta. Tieto siitä, kuinka virtuaalikone edustaa pinoaan, häiritsee todennäköisemmin kehittäjien huomion, jotka tekevät älykkäitä sopimuksia TON-alustalle, kuin auttavat heitä.

Nix: projektin yhdistäminen

Me Serokellilla olemme suuria faneja ei käy. Keräämme projektimme sen kanssa ja otamme ne käyttöön NixOps, ja asennettu kaikille palvelimillemme Nix OS. Tämän ansiosta kaikki rakennelmamme ovat toistettavissa ja toimivat kaikissa käyttöjärjestelmissä, joihin Nix voidaan asentaa.

Joten aloitimme luomalla Nix-päällys, jossa on lauseke TON-kokoonpanoa varten. Sen avulla TONin kääntäminen on mahdollisimman yksinkertaista:

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

Huomaa, että sinun ei tarvitse asentaa mitään riippuvuuksia. Nix tekee taianomaisesti kaiken puolestasi, olitpa sitten käytössä NixOS, Ubuntu tai macOS.

Ohjelmointi TONille

TON-verkon älykäs sopimuskoodi toimii TON Virtual Machinessa (TVM). TVM on monimutkaisempi kuin useimmat muut virtuaalikoneet, ja siinä on esimerkiksi erittäin mielenkiintoisia toimintoja, joiden kanssa se voi toimia jatkot и linkkejä dataan.

Lisäksi TONin kaverit loivat kolme uutta ohjelmointikieltä:

Viisi on universaali pinoohjelmointikieli, joka muistuttaa eteenpäin. Hänen superkykynsä on kyky olla vuorovaikutuksessa TVM:n kanssa.

FunC on älykäs sopimusohjelmointikieli, joka on samanlainen kuin C ja se on käännetty toiselle kielelle - Fift Assembler.

Fifth Assembler — Fift-kirjasto binaarisen suoritettavan koodin luomiseen TVM:lle. Fifth Assemblerilla ei ole kääntäjää. Tämä Embedded Domain Specific Language (eDSL).

Kilpailumme toimii

Lopuksi on aika tarkastella ponnistelujemme tuloksia.

Asynkroninen maksukanava

Maksukanava on älykäs sopimus, jonka avulla kaksi käyttäjää voivat lähettää maksuja lohkoketjun ulkopuolelle. Tämän seurauksena säästät paitsi rahaa (ei provisiota), myös aikaa (sinun ei tarvitse odottaa seuraavan lohkon käsittelyä). Maksut voivat olla niin pieniä kuin halutaan ja niin usein kuin tarvitaan. Tällöin osapuolten ei tarvitse luottaa toisiinsa, sillä älykäs sopimus takaa loppuratkaisun oikeudenmukaisuuden.

Löysimme ongelmaan melko yksinkertaisen ratkaisun. Kaksi osapuolia voivat vaihtaa allekirjoitettuja viestejä, joista kukin sisältää kaksi numeroa – kummankin osapuolen maksaman täyden summan. Nämä kaksi numeroa toimivat kuten vektori kello perinteisissä hajautetuissa järjestelmissä ja aseta tapahtumille "tapahtui ennen" -järjestys. Näiden tietojen avulla sopimus pystyy ratkaisemaan mahdolliset ristiriidat.

Itse asiassa yksi numero riittää toteuttamaan tämän idean, mutta jätimme molemmat, koska näin voisimme tehdä käyttöliittymästä mukavamman. Lisäksi päätimme sisällyttää jokaiseen viestiin maksusumman. Ilman sitä, jos viesti jostain syystä katoaa, niin vaikka kaikki summat ja lopullinen laskelma ovat oikein, käyttäjä ei välttämättä huomaa menetystä.

Testataksemme ideaamme etsimme esimerkkejä tällaisen yksinkertaisen ja tiiviin maksukanavaprotokollan käytöstä. Yllättäen löysimme vain kaksi:

  1. Kuvaus samanlainen lähestymistapa, vain yksisuuntaisen kanavan tapauksessa.
  2. Opastus, joka kuvaa samaa ideaa kuin meidän, mutta selittämättä monia tärkeitä yksityiskohtia, kuten yleistä oikeellisuutta ja konfliktien ratkaisumenettelyjä.

Kävi selväksi, että on järkevää kuvata protokollamme yksityiskohtaisesti kiinnittäen erityistä huomiota sen oikeellisuuteen. Useiden iteraatioiden jälkeen spesifikaatio oli valmis, ja nyt sinäkin voit. Katso häntä.

Toteutimme sopimuksen FunC:ssä ja kirjoitimme komentorivityökalun sopimukseemme vuorovaikutukseen kokonaan Fiftissä järjestäjien suosittelemalla tavalla. Olisimme voineet valita minkä tahansa muun kielen CLI:llemme, mutta olimme kiinnostuneita kokeilemaan Fitiä nähdäksemme, kuinka se toimii käytännössä.

Ollakseni rehellinen, työskenneltyämme Fiftin kanssa emme nähneet mitään pakottavia syitä suosia tätä kieltä suosittujen ja aktiivisesti käytettyjen kielten sijaan kehitettyjen työkalujen ja kirjastojen avulla. Ohjelmointi pinopohjaisella kielellä on varsin epämiellyttävää, koska sinun täytyy jatkuvasti pitää päässäsi, mitä pinossa on, eikä kääntäjä auta tässä.

Siksi mielestämme ainoa peruste Fiftin olemassaololle on sen rooli Fift Assemblerin isäntäkielenä. Mutta eikö olisi parempi upottaa TVM-asentaja johonkin olemassa olevaan kieleen sen sijaan, että keksittäisiin uusi tähän olennaisesti ainoaan tarkoitukseen?

TVM Haskell eDSL

Nyt on aika puhua toisesta älykkäästä sopimuksestamme. Päätimme kehittää usean allekirjoituksen lompakon, mutta uuden älykkään sopimuksen kirjoittaminen FunC:ssa olisi liian tylsää. Halusimme lisätä makua, ja se oli oma kokoonpanokielemme TVM:lle.

Kuten Fift Assembler, uusi kielemme on upotettu, mutta valitsimme isäntäksi Haskellin Fiftin sijaan, jolloin voimme hyödyntää täysimääräisesti sen kehittynyttä tyyppijärjestelmää. Kun työskentelet älykkäiden sopimusten kanssa, joissa pienestäkin virheestä aiheutuvat kustannukset voivat olla erittäin korkeat, staattinen kirjoittaminen on mielestämme suuri etu.

Osoittaaksemme, miltä TVM assembler näyttää Haskelliin upotettuna, toteutimme siihen tavallisen lompakon. Tässä on muutamia asioita, joihin kannattaa kiinnittää huomiota:

  • Tämä sopimus koostuu yhdestä toiminnosta, mutta voit käyttää niin montaa kuin haluat. Kun määrität uuden toiminnon isäntäkielellä (esim. Haskell), eDSL:ssämme voit valita, haluatko siitä tulla erilliseksi rutiiniksi TVM:ssä vai yksinkertaisesti sisällytettäväksi soittopisteeseen.
  • Kuten Haskell, funktioilla on tyyppejä, jotka tarkistetaan käännöshetkellä. eDSL:ssämme funktion syöttötyyppi on pinon tyyppi, jota funktio odottaa, ja tulostyyppi on pinon tyyppi, joka tuotetaan kutsun jälkeen.
  • Koodissa on huomautuksia stacktype, joka kuvaa odotettua pinotyyppiä kutsupisteessä. Alkuperäisessä lompakkosopimuksessa nämä olivat vain kommentteja, mutta eDSL:ssämme ne ovat itse asiassa osa koodia ja tarkistetaan käännösvaiheessa. Ne voivat toimia asiakirjoina tai lausuntoina, jotka auttavat kehittäjää löytämään ongelman, jos koodi muuttuu ja pinotyyppi muuttuu. Tällaiset merkinnät eivät tietenkään vaikuta ajonaikaiseen suorituskykyyn, koska niille ei luoda TVM-koodia.
  • Tämä on vielä kahdessa viikossa kirjoitettu prototyyppi, joten projektissa on vielä paljon tehtävää. Esimerkiksi kaikki alla olevassa koodissa näkemiesi luokkien esiintymät tulisi luoda automaattisesti.

Tältä multisig-lompakon toteutus näyttää eDSL:ssämme:

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

eDSL- ja usean allekirjoituksen lompakkosopimuksemme koko lähdekoodi löytyy osoitteesta tämä arkisto. Ja enemmän kerrottu yksityiskohtaisesti sisäänrakennetuista kielistä, kollegamme Georgy Agapov.

Päätelmät kilpailusta ja TON:sta

Työmme kesti yhteensä 380 tuntia (sisältäen dokumentaatioon tutustumisen, tapaamiset ja varsinaisen kehityksen). Kilpailuprojektiin osallistui viisi kehittäjää: CTO, tiiminvetäjä, blockchain-alustan asiantuntijat ja Haskell-ohjelmistokehittäjät.

Löysimme resursseja osallistua kilpailuun vaivattomasti, sillä hackathonin henki, tiivis ryhmätyö ja tarve nopeasti uppoutua uusiin teknologioihin on aina jännittävää. Useita unettomia öitä maksimaalisten tulosten saavuttamiseksi rajallisten resurssien olosuhteissa korvataan korvaamattomalla kokemuksella ja erinomaisilla muistoilla. Lisäksi tällaisten tehtävien parissa työskentely on aina hyvä testi yrityksen prosesseille, sillä todella kunnollisia tuloksia on erittäin vaikea saavuttaa ilman hyvin toimivaa sisäistä vuorovaikutusta.

Sanat sivuun: olimme vaikuttuneita TON-tiimin tekemästä työmäärästä. He onnistuivat rakentamaan monimutkaisen, kauniin ja mikä tärkeintä toimivan järjestelmän. TON on osoittanut olevansa alusta, jolla on paljon potentiaalia. Kuitenkin, jotta tämä ekosysteemi kehittyisi, on tehtävä paljon enemmän sekä sen käytön lohkoketjuprojekteissa että kehitystyökalujen parantamisen suhteen. Olemme nyt ylpeitä saadessamme olla osa tätä prosessia.

Jos sinulla on tämän artikkelin lukemisen jälkeen vielä kysymyksiä tai ideoita TONin käyttämisestä ongelmien ratkaisemiseen, Kirjoittakaa meille – Jaamme mielellämme kokemuksiamme.

Lähde: will.com

Lisää kommentti