At forvandle FunC til FunCtional med Haskell: hvordan Serokell vandt Telegram Blockchain Competition

Du har sikkert hørt om, at Telegram planlægger at lancere blockchain-platformen Ton. Men du kunne have overset nyheden om, at Telegram for nylig har annonceret en konkurrence for implementering af en eller flere smart contracts til denne platform.

Teamet hos Serokell, med stor erfaring i udviklingen af store blockchain-projekter, kunne ikke lade være med at deltage. Vi sendte fem medarbejdere til konkurrencen, og allerede to uger senere indtog de førstepladsen under det (u)beskedne tilfældige alias Sexy Chameleon. I denne artikel vil jeg fortælle, hvordan de gjorde det. Vi håber, at du i løbet af de næste ti minutter vil læse en interessant historie, og måske finde noget nyttigt, som du kan anvende i dit arbejde.

Men lad os begynde med et lille dyk ind i konteksten.

Konkurrencen og dens betingelser

Så hovedopgaverne for deltagerne var implementeringen af en eller flere af de foreslåede smart contracts samt at komme med forslag til forbedring af TON-økosystemet. Konkurrencen løb fra 24. september til 15. oktober, og resultaterne blev først offentliggjort den 15. november. Temmelig lang tid, når man overvejer, at Telegram i mellemtiden nåede at gennemføre og offentliggøre resultaterne af designkonkurrence og udviklingen af applikationer i C++ til testning og vurdering af kvaliteten af VoIP-opkald i Telegram.

Vi valgte to smart contracts fra listen, der blev foreslået af arrangørerne. Til den ene brugte vi værktøjer, der blev distribueret sammen med TON, og den anden blev implementeret på et nyt sprog, som vores ingeniører udviklede specielt til TON og indarbejdede i Haskell.

Valget af funktionelt programmeringssprog er ikke tilfældigt. I vores virksomhedsblog fortæller vi ofte, hvorfor vi mener, at kompleksiteten af funktionelle sprog er en stor overdrivelse, og hvorfor vi generelt foretrækker dem frem for objektorienterede. For resten er der også en original af denne artikel.

Hvorfor besluttede vi overhovedet at deltage

Kort sagt, fordi vores specialisering er usædvanlige og komplekse projekter, der kræver særlige færdigheder og ofte repræsenterer videnskabelig værdi for IT-samfundet. Vi bakker varmt op om open-source udvikling og arbejder på at popularisere det, samt samarbejder med førende universiteter i Rusland inden for computer science og matematik.

De interessante opgaver i konkurrencen og tilknytningen til det projekt, vi holder så meget af, Telegram, var i sig selv en fantastisk motivation, og præmiepuljen blev et ekstra incitament. 🙂

Undersøgelse af TON blockchain

Vi holder nøje øje med nye udviklinger inden for blockchain, kunstig intelligens og maskinindlæring, og vi forsøger ikke at gå glip af en eneste betydningsfuld udgivelse inden for hver af de områder, vi arbejder med. Derfor var vores team allerede bekendt med ideerne fra TON white paper. Dog havde vi ikke analyseret den tekniske dokumentation og den faktiske kildekode til platformen, før vi begyndte at arbejde med TON, så det første skridt var ret åbenlyst — en grundig undersøgelse af den officielle dokumentation på websitet og i projektets repository.

Ved begyndelsen af konkurrencen var koden allerede offentliggjort, så for at spare tid besluttede vi at lede efter en vejledning eller et resume skrevet af brugere. Desværre gav dette ikke noget resultat — vi fandt ikke andet end instruktioner til at samle platformen på Ubuntu.

Selve dokumentationen viste sig at være grundigt udarbejdet, men det var besværligt at læse den i nogle tilfælde. Ret ofte måtte vi vende tilbage til forskellige punkter og skifte fra højniveaubeskrivelser af abstrakte ideer til lavniveau detaljer om implementeringen.

Det ville have været lettere, hvis specifikationen slet ikke havde detaljerede beskrivelser af implementeringen. Oplysninger om, hvordan den virtuelle maskine repræsenterer sin stak, distraherer snarere udviklere, der skaber smarte kontrakter til platformen TON, end det hjælper dem.

Nix: vi samler projektet

Hos Serokell er vi store tilhængere Nix. Vi samler vores projekter med dem og implementerer dem ved hjælp af NixOps, og der er installeret NixOSpå alle vores servere. Takket være dette er alle vores builds reproducerbare og fungerer på ethvert operativsystem, som Nix kan installeres på.

Derfor begyndte vi med at oprette Nix overlay med en udtryk til at bygge TON. Med det er det ekstremt enkelt at kompilere TON:

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

Bemærk, at du ikke behøver at installere nogen afhængigheder. Nix vil magisk sørge for alt for dig, uanset om du bruger NixOS, Ubuntu eller macOS.

Programmering for TON

Koden for smarte kontrakter i TON Network udføres på TON Virtual Machine (TVM). TVM er mere kompleks end de fleste andre virtuelle maskiner og har en del interessante funktioner, såsom at den kan arbejde med fortsættelser (continuations) og datahenvisninger.

Desuden har folkene fra TON skabt hele tre nye programmeringssprog:

Fift — et universelt stak-baseret programmeringssprog, der minder om Forth. Dets superkraft er muligheden for at interagere med TVM.

FunC — et programmeringssprog til smarte kontrakter, der ligner C og kompileres til endnu et sprog — Fift Assembler.

Fift Assembler — en Fift-bibliotek til generering af binær eksekverbar kode til TVM. Fift Assembler har ikke en compiler. Det er et indlejret domænespecifikt sprog (eDSL).

Vores konkurrenceprojekter

Endelig er det tid til at se på resultaterne af vores bestræbelser.

Asynkront betalingskanal

En betalingskanal (payment channel) er en smart kontrakt, der giver to brugere mulighed for at sende betalinger uden for blockchainen. Dette sparer ikke kun penge (ingen gebyrer), men også tid (du behøver ikke vente på, at den næste blok behandles). Betalinger kan være så små som ønskes og ske så ofte som nødvendigt. Desuden er det ikke nødvendigt for parterne at stole på hinanden, da retfærdigheden i den endelige afregning garanteres af smart kontrakten.

Vi har fundet en ret simpel løsning på problemet. De to parter kan udveksle underskrevne meddelelser, hvoraf hver indeholder to tal — det samlede beløb betalt af hver deltager. Disse to tal fungerer som vektoriske ure i traditionelle distribuerede systemer og angiver rækkefølgen "skete før" på transaktionerne. Ved at bruge disse data kan kontrakten løse enhver mulig konflikt.

Faktisk er det nok med et eneste tal for at implementere denne idé, men vi har efterladt begge, da vi derved kunne lave en mere brugervenlig grænseflade. Derudover besluttede vi at inkludere størrelsen af betalingen i hver meddelelse. Uden dette, hvis meddelelsen af en eller anden grund går tabt, vil brugeren måske ikke bemærke tabet, selvom alle beløb og den endelige afregning er korrekte.

For at teste vores idé, søgte vi efter eksempler på brugen af en så simpel og ligetil betalingskanalprotokol. Til vores overraskelse fandt vi kun to:

  1. Description en lignende tilgang, kun for en ensrettet kanal.
  2. Tutorial, som beskriver den samme idé som vores, men uden forklaring af mange vigtige detaljer, såsom den overordnede korrekthed og proceduren til konfliktløsning.

Det blev klart, at det giver mening at beskrive vores protokol detaljeret med særlig vægt på dens korrekthed. Efter flere iterationer var specifikationen klar, og nu kan du også se på den.

Vi implementerede kontrakten i FunC, og kommandolinjeværktøjet til at interagere med vores kontrakt skrev vi helt i Fift, som arrangørerne anbefalede. Vi kunne have valgt et andet sprog til vores CLI, men vi var nysgerrige efter at prøve Fift for at se, hvordan det ville præstere i praksis.

Ærligt talt, efter at have arbejdet med Fift, så vi ingen overbevisende grunde til at foretrække dette sprog fremfor mere populære og aktivt anvendte sprog med udviklede værktøjer og biblioteker. At programmere i et stakbaseret sprog er temmelig ubehageligt, da man konstant skal holde styr på, hvad der ligger hvor i stakken, og kompilereren hjælper ikke med dette.

Derfor mener vi, at den eneste rimelige grund til Fifts eksistens er dens rolle som værtsprog for Fift Assembler. Men ville det ikke have været bedre at indbygge TVM-assembleren i et eksisterende sprog i stedet for at finde på et nyt til dette i bund og grund unikke formål?

TVM Haskell eDSL

Nu er det tid til at fortælle om vores anden smarte kontrakt. Vi besluttede at udvikle en multisignaturtegneboks, men at skrive endnu en smart kontrakt i FunC ville være for kedeligt. Vi ønskede at tilføje noget specielt, og det blev vores eget assembler-sprog til TVM.

Ligesom Fift Assembler er vores nye sprog indlejret, men i stedet for Fift valgte vi Haskell som vært, hvilket gjorde os i stand til fuldt ud at udnytte dets avancerede typesystem. Når man arbejder med smarte kontrakter, hvor omkostningerne ved selv en lille fejl kan være meget høje, mener vi, at statisk typisering er en stor fordel.

For at demonstrere, hvordan TVM-assembleren indbygget i Haskell ser ud, har vi implementeret en standard tegnebog. Her er nogle ting, du skal være opmærksom på:

  • Denne kontrakt består af én funktion, men du kan bruge så mange du vil. Når du definerer en ny funktion på værtsproget (det vil sige Haskell), giver vores eDSL dig mulighed for at vælge, om den skal blive til et separat underprogram i TVM eller blot indleires på stedet, hvor den bliver kaldt.
  • Ligesom i Haskell har funktioner typer, der bliver kontrolleret under kompileringen. I vores eDSL er typen for funktionsindgangen den type stak, som funktionen forventer, mens typen for resultatet er den type stak, som vil resultere efter kaldet.
  • Der er annoteringer i koden stacktype, der beskriver den forventede staktype på kaldestedet. I den originale tegnebogskontrakt var de blot kommentarer, men i vores eDSL er de faktisk en del af koden og bliver kontrolleret under kompileringen. De kan fungere som dokumentation eller påstande, der hjælper udvikleren med at finde problemer, hvis staktypen ændres, når koden ændres. Selvfølgelig påvirker sådanne annoteringer ikke ydeevnen under udførelsen, da der ikke genereres nogen TVM-kode for dem.
  • Det er stadig en prototype, der er skrevet på to uger, så der er stadig meget arbejde at gøre på projektet. For eksempel skal alle klasseinstanser, som du ser i den nedenstående kode, genereres automatisk.

Her er, hvordan implementeringen af multisig-tegnebog ser ud i vores 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

Den fulde kildekode til vores eDSL og wallet kontrakt med multisignatur kan findes i denne repository. En mere detaljeret gennemgang blev givet af vores kollega Georgiy Agapov.

Konklusioner om konkurrencen og TON

I alt tog vores arbejde 380 timer (inklusive at sætte sig ind i dokumentationen, møder og selve udviklingen). Fem udviklere deltog i konkurrenceprojektet: CTO, teamleder, specialister i blockchain-platforme og softwareudviklere med Haskell.

Vi fandt let de ressourcer, der var nødvendige for at deltage i konkurrencen, da ånden fra hackathonet, tæt teamwork og nødvendigheden af hurtig indsigt i nye teknologiers aspekter altid er spændende. Flere søvnløse nætter for at opnå det maksimale resultat under begrænsede ressourcer kompenseres af uvurderlig erfaring og gode minder. Endvidere er arbejdet med sådanne opgaver altid en god test af virksomhedens processer, da det er ekstremt svært at opnå virkelig værdifulde resultater uden en velfungerende intern interaktion.

Udover lyrikken: vi var imponerede over mængden af arbejde, som TON-teamet havde udført. De formåede at bygge et komplekst, smukt, og vigtigst af alt, fungerende system. TON har vist sig at være en platform med stort potentiale. Men for at dette økosystem kan udvikle sig, er der stadig meget, der skal gøres, både med hensyn til dets anvendelse i blockchain-projekter og forbedring af udviklingsværktøjerne. Vi er stolte af at være en del af denne proces nu.

Hvis du efter at have læst denne artikel har spørgsmål eller ideer til, hvordan TON kan hjælpe dig med at løse dine opgaver, skriv til os — vi deler gerne vores erfaring.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster