FunC muutmine funktsionaalseks koos Haskelliga: kuidas Serokell võitis Telegrami plokiahela konkursi

Tõenäoliselt olete seda Telegrami kuulnud käivitab Ton plokiahela platvormi. Kuid võisite nägemata jätta seda uudist, mis hiljuti oli Telegram kuulutas välja konkursi selle platvormi jaoks ühe või mitme nutika lepingu rakendamiseks.

Serokelli meeskond, kellel on laialdased kogemused suurte plokiahelaprojektide arendamisel, ei suutnud kõrvale seista. Delegeerisime konkursile viis töötajat ja kaks nädalat hiljem saavutasid nad sellel esikoha (eba)tagasihoidliku juhusliku hüüdnimega Sexy Chameleon. Selles artiklis räägin sellest, kuidas nad seda tegid. Loodame, et järgmise kümne minuti jooksul loete vähemalt mõnda huvitavat lugu ja maksimaalselt leiate sellest midagi kasulikku, mida saate oma töös rakendada.

Kuid alustame väikese kontekstiga.

Konkurents ja selle tingimused

Seega oli osalejate peamisteks ülesanneteks ühe või mitme pakutud nutika lepingu rakendamine, samuti ettepanekute tegemine TONi ökosüsteemi parandamiseks. Konkurss kestis 24. septembrist 15. oktoobrini ning tulemused tehti teatavaks alles 15. novembril. Üsna pikka aega, arvestades, et selle aja jooksul õnnestus Telegramil korraldada ja välja kuulutada konkursid C++-s VoIP-kõnede kvaliteedi testimiseks ja kvaliteedi hindamiseks C++-s.

Valisime korraldajate pakutud nimekirjast välja kaks nutikat lepingut. Neist ühe jaoks kasutasime TON-iga levitatud tööriistu ja teine ​​rakendati meie inseneride poolt spetsiaalselt TONi jaoks välja töötatud ja Haskelli sisse ehitatud uues keeles.

Funktsionaalse programmeerimiskeele valik pole juhuslik. Meie ettevõtte ajaveeb Me räägime sageli sellest, miks me arvame, et funktsionaalsete keelte keerukus on suur liialdus ja miks me üldiselt eelistame neid objektorienteeritud keeltele. Muide sisaldab ka selle artikli originaal.

Miks me üldse osaleda otsustasime?

Ühesõnaga, kuna meie spetsialiseerumine on ebastandardsed ja keerukad projektid, mis nõuavad erioskusi ning on IT-kogukonna jaoks sageli teadusliku väärtusega. Toetame tugevalt avatud lähtekoodiga arendamist ja tegeleme selle populariseerimisega, samuti teeme koostööd juhtivate Venemaa ülikoolidega arvutiteaduse ja matemaatika vallas.

Konkursi huvitavad ülesanded ja kaasalöömine meie armastatud Telegrami projektis olid iseenesest suurepäraseks motivatsiooniks, kuid auhinnafond sai lisastiimuliks. 🙂

TON plokiahela uurimine

Jälgime tähelepanelikult plokiahela, tehisintellekti ja masinõppe uusi arenguid ning püüame mitte jätta kahe silma vahele ühtegi olulist väljalaset igas valdkonnas, kus me töötame. Seega, võistluse alguseks olid meie meeskonnal ideed juba tuttavad TON valget paberit. Kuid enne TONiga töö alustamist ei analüüsinud me tehnilist dokumentatsiooni ja platvormi tegelikku lähtekoodi, seega oli esimene samm üsna ilmne - ametliku dokumentatsiooni põhjalik uurimine veebisait ja projektihoidlad.

Konkursi alguse ajaks oli kood juba avaldatud, seega otsustasime aja kokkuhoiu mõttes otsida juhendi või kokkuvõtte, mille on kirjutanud kasutajate poolt. Kahjuks ei andnud see tulemusi – peale Ubuntu platvormi kokkupanemise juhiste me muid materjale ei leidnud.

Dokumentatsioon ise oli põhjalikult läbi uuritud, kuid mõnes valdkonnas raskesti loetav. Üsna sageli tuli naasta teatud punktide juurde ja minna üle abstraktsete ideede kõrgetasemelistest kirjeldustest madalatasemeliste teostusdetailide vastu.

Lihtsam oleks, kui spetsifikatsioon ei sisaldaks üldse detailset teostuse kirjeldust. Teave selle kohta, kuidas virtuaalmasin oma pinu esindab, tõmbab TON-platvormi jaoks nutikaid lepinguid loovate arendajate tähelepanu pigem kõrvale kui aitab neid.

Nix: projekti kokku panemine

Serokellis oleme suured fännid Nix. Kogume sellega oma projektid kokku ja juurutame neid kasutades NixOpsja installitud kõikidesse meie serveritesse Nix OS. Tänu sellele on kõik meie versioonid reprodutseeritavad ja töötavad mis tahes operatsioonisüsteemiga, millele saab Nixi installida.

Nii et alustasime loomisest Nix-ülekate väljendiga TON-i montaaži jaoks. Tema abiga on TONi koostamine võimalikult lihtne:

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

Pange tähele, et te ei pea installima mingeid sõltuvusi. Nix teeb teie eest võluväel kõik, olenemata sellest, kas kasutate NixOS-i, Ubuntut või macOS-i.

Programmeerimine TONi jaoks

TON-võrgu nutikas lepingukood töötab TON-i virtuaalmasinas (TVM). TVM on keerulisem kui enamik teisi virtuaalmasinaid ja sellel on näiteks väga huvitav funktsionaalsus, millega see võib töötada jätkud и lingid andmetele.

Lisaks lõid TONi poisid kolm uut programmeerimiskeelt:

Viies on universaalne pinu programmeerimiskeel, mis sarnaneb Edasi. Tema supervõime on võime suhelda TVM-iga.

FunC on nutikas lepinguline programmeerimiskeel, mis sarnaneb C ja see on koostatud teise keelde - Fift Assembler.

Viies kokkupanija — Viie teek binaarse käivitatava koodi genereerimiseks TVM-i jaoks. Fifth Assembleril pole kompilaatorit. See Manustatud domeenispetsiifiline keel (eDSL).

Meie konkurss töötab

Lõpuks on aeg vaadata meie jõupingutuste tulemusi.

Asünkroonne maksekanal

Maksekanal on nutikas leping, mis võimaldab kahel kasutajal saata makseid väljaspool plokiahelat. Selle tulemusel säästate mitte ainult raha (vahendustasu ei ole), vaid ka aega (te ei pea ootama järgmise ploki töötlemist). Maksed võivad olla nii väikesed kui soovitakse ja nii tihti kui vaja. Sel juhul ei pea pooled üksteist usaldama, kuna lõpparve õigluse tagab nutikas leping.

Leidsime probleemile üsna lihtsa lahenduse. Kaks osapoolt saavad vahetada allkirjastatud sõnumeid, millest kumbki sisaldab kahte numbrit – kummagi poole makstavat kogusummat. Need kaks numbrit töötavad nagu vektorkell traditsioonilistes hajutatud süsteemides ja määrake tehingutele "toimunud enne" järjekord. Neid andmeid kasutades saab leping lahendada kõik võimalikud konfliktid.

Tegelikult piisab selle idee elluviimiseks ühest numbrist, kuid jätsime mõlemad pooleli, sest nii saime teha mugavama kasutajaliidese. Lisaks otsustasime igasse sõnumisse lisada maksesumma. Ilma selleta, kui sõnum läheb mingil põhjusel kaduma, siis kuigi kõik summad ja lõplik arvutus on õiged, ei pruugi kasutaja kaotust märgata.

Oma idee testimiseks otsisime näiteid sellise lihtsa ja ülevaatliku maksekanali protokolli kasutamisest. Üllataval kombel leidsime ainult kaks:

  1. Kirjeldus sarnane lähenemine, ainult ühesuunalise kanali puhul.
  2. Õpetus, mis kirjeldab meiega sama ideed, kuid ei selgitanud paljusid olulisi detaile, nagu üldine korrektsus ja konfliktide lahendamise protseduurid.

Sai selgeks, et meie protokolli on mõistlik üksikasjalikult kirjeldada, pöörates erilist tähelepanu selle õigsusele. Pärast mitut iteratsiooni oli spetsifikatsioon valmis ja nüüd saate ka seda teha. Vaata teda.

Rakendasime lepingu FunC-s ja kirjutasime oma lepinguga suhtlemiseks käsurea utiliidi täielikult Fiftis, nagu korraldajad soovitasid. Oleksime võinud oma CLI jaoks valida mis tahes muu keele, kuid olime huvitatud Fiti proovimisest, et näha, kuidas see praktikas toimib.

Ausalt öeldes ei näinud me pärast Fiftiga töötamist ühtegi kaalukat põhjust eelistada seda keelt populaarsetele ja aktiivselt kasutatavatele keeltele koos arendatud tööriistade ja raamatukogudega. Pinupõhises keeles programmeerimine on üsna ebameeldiv, kuna pead pidevalt peas hoidma pinu peal olevat ja kompilaator selle vastu ei aita.

Seetõttu on meie arvates ainus õigustus Fifti olemasolule selle roll Fift Assembleri hostkeelena. Kuid kas poleks parem manustada TVM-i assembler mõnda olemasolevasse keelde, selle asemel, et leiutada uus selle sisuliselt ainsa eesmärgi jaoks?

TVM Haskell eDSL

Nüüd on aeg rääkida meie teisest nutikast lepingust. Otsustasime välja töötada mitme allkirjaga rahakoti, kuid FunC-s järjekordse nutika lepingu kirjutamine oleks liiga igav. Tahtsime lisada veidi maitset ja see oli meie enda koostekeel TVMi jaoks.

Nagu Fift Assembler, on ka meie uus keel manustatud, kuid valisime hostiks Fifti asemel Haskelli, mis võimaldab meil selle täiustatud tüübisüsteemi kõiki eeliseid kasutada. Töötades nutikate lepingutega, kus isegi väikese vea hind võib olla väga kõrge, on staatiline tippimine meie arvates suur eelis.

Et demonstreerida, kuidas Haskelli manustatud TVM-i assembler välja näeb, rakendasime sellele standardse rahakoti. Siin on mõned asjad, millele tähelepanu pöörata.

  • See leping koosneb ühest funktsioonist, kuid võite kasutada nii palju kui soovite. Kui defineerite hostkeeles (st Haskellis) uue funktsiooni, võimaldab meie eDSL teil valida, kas soovite, et see muutuks TVM-is eraldi rutiiniks või lisataks see lihtsalt kõnepunkti.
  • Nagu Haskellil, on funktsioonidel tüübid, mida kontrollitakse kompileerimise ajal. Meie eDSL-is on funktsiooni sisendtüüp pinu tüüp, mida funktsioon ootab, ja tulemuse tüüp on virna tüüp, mis luuakse pärast kõnet.
  • Koodil on märkused stacktype, mis kirjeldab kutsumispunkti eeldatavat virna tüüpi. Algses rahakotilepingus olid need vaid kommentaarid, kuid meie eDSL-is on need tegelikult koodi osa ja neid kontrollitakse kompileerimise ajal. Neid saab kasutada dokumentatsiooni või avaldustena, mis aitavad arendajal koodi muutumisel ja virnatüübi muutumisel probleemi leida. Loomulikult ei mõjuta sellised märkused käitusaja jõudlust, kuna nende jaoks ei genereerita TVM-koodi.
  • Tegemist on ikkagi kahe nädalaga kirjutatud prototüübiga, nii et projektiga on veel palju tööd teha. Näiteks kõik klasside eksemplarid, mida näete allolevas koodis, tuleks genereerida automaatselt.

Nii näeb multisig-rahakoti rakendamine meie eDSL-is välja:

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

Meie eDSL-i ja mitme allkirjaga rahakoti lepingu täieliku lähtekoodi leiate aadressilt see hoidla. Ja veel üksikasjalikult räägitud sisseehitatud keelte kohta meie kolleeg Georgi Agapov.

Järeldused konkursi ja TONi kohta

Kokku kestis meie töö 380 tundi (sh dokumentatsiooniga tutvumine, koosolekud ja tegelik arendus). Võistlusprojektis osales viis arendajat: CTO, meeskonna juht, plokiahela platvormi spetsialistid ja Haskelli tarkvaraarendajad.

Leidsime ressursse, et konkursil osaleda raskusteta, sest häkatoni vaim, tihe meeskonnatöö ja vajadus kiiresti uute tehnoloogiate aspektidesse sukelduda on alati põnev. Mitu magamata ööd maksimaalsete tulemuste saavutamiseks piiratud ressursside tingimustes kompenseeritakse hindamatute kogemuste ja suurepäraste mälestustega. Lisaks on selliste ülesannete kallal töötamine alati ettevõtte protsesside heaks proovikiviks, kuna tõeliselt korralike tulemuste saavutamine ilma hästitoimiva sisemise suhtluseta on äärmiselt keeruline.

Laulusõnad kõrvale: meile avaldas muljet TONi meeskonna tehtud töömaht. Neil õnnestus ehitada keeruline, ilus ja mis kõige tähtsam, töötav süsteem. TON on tõestanud end suure potentsiaaliga platvormina. Selle ökosüsteemi arendamiseks on aga vaja palju rohkem ära teha nii selle kasutamise osas plokiahela projektides kui ka arendusvahendite täiustamise osas. Oleme uhked, et saame nüüd sellest protsessist osa.

Kui pärast selle artikli lugemist on teil endiselt küsimusi või ideid, kuidas TON-i oma probleemide lahendamiseks kasutada, kirjuta meile — jagame hea meelega oma kogemusi.

Allikas: www.habr.com

Lisa kommentaar