Gør FunC til FunCtional med Haskell: Hvordan Serokell vandt Telegram Blockchain-konkurrencen

Du har sikkert hørt det Telegram er ved at lancere Ton blockchain-platformen. Men du er måske gået glip af nyheden om, at Telegram for ikke så længe siden annonceret en konkurrence til implementering af en eller flere smarte kontrakter til denne platform.

Serokell-teamet, med stor erfaring i at udvikle store blockchain-projekter, kunne ikke stå til side. Vi uddelegerede fem medarbejdere til konkurrencen, og to uger senere tog de førstepladsen i den under det (u)beskedne tilfældige øgenavn Sexy Chameleon. I denne artikel vil jeg fortælle om, hvordan de gjorde det. Vi håber, at du inden for de næste ti minutter i det mindste vil læse en interessant historie, og højst vil du finde noget brugbart i den, som du kan anvende i dit arbejde.

Men lad os starte med en lille kontekst.

Konkurrencen og dens betingelser

Så deltagernes hovedopgaver var implementeringen af ​​en eller flere af de foreslåede smarte kontrakter samt at fremsætte 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 i betragtning af, at Telegram i løbet af denne tid formåede at afholde og annoncere resultaterne af konkurrencer om design og udvikling af applikationer i C++ til test og vurdering af kvaliteten af ​​VoIP-opkald i Telegram.

Vi valgte to smarte kontrakter fra listen foreslået af arrangørerne. Til et af dem brugte vi værktøjer distribueret med TON, og det andet blev implementeret i et nyt sprog udviklet af vores ingeniører specifikt til TON og indbygget i Haskell.

Valget af et funktionelt programmeringssprog er ikke tilfældigt. I vores virksomhedens blog Vi taler ofte om, hvorfor vi synes kompleksiteten af ​​funktionelle sprog er en stor overdrivelse, og hvorfor vi generelt foretrækker dem frem for objektorienterede. Det indeholder i øvrigt også original af denne artikel.

Hvorfor besluttede vi overhovedet at deltage?

Kort sagt fordi vores specialisering er ikke-standardiserede og komplekse projekter, der kræver særlige kompetencer og ofte er af videnskabelig værdi for IT-miljøet. Vi støtter stærkt open source-udvikling og er engageret i dens popularisering og samarbejder også med førende russiske universiteter inden for datalogi og matematik.

Konkurrencens interessante opgaver og involvering i vores elskede Telegram-projekt var i sig selv en fremragende motivation, men præmiefonden blev et yderligere incitament. 🙂

TON blockchain forskning

Vi overvåger nøje nye udviklinger inden for blockchain, kunstig intelligens og maskinlæring og forsøger ikke at gå glip af en eneste væsentlig udgivelse inden for hvert af de områder, vi arbejder inden for. Derfor, da konkurrencen startede, var vores team allerede bekendt med ideer fra TON hvidt papir. Men før vi startede arbejdet med TON, analyserede vi ikke den tekniske dokumentation og den faktiske kildekode for platformen, så det første skridt var ret indlysende - en grundig undersøgelse af den officielle dokumentation vedr. Online og projektdepoter.

Da konkurrencen startede, var koden allerede blevet offentliggjort, så for at spare tid besluttede vi at lede efter en guide eller et resumé skrevet af af brugere. Dette gav desværre ingen resultater - udover instruktioner til montering af platformen på Ubuntu, fandt vi ikke andre materialer.

Selve dokumentationen var godt undersøgt, men var svær at læse på nogle områder. Ganske ofte måtte vi vende tilbage til bestemte punkter og skifte fra beskrivelser på højt niveau af abstrakte ideer til implementeringsdetaljer på lavt niveau.

Det ville være nemmere, hvis specifikationen slet ikke indeholdt en detaljeret beskrivelse af implementeringen. Oplysninger om, hvordan en virtuel maskine repræsenterer sin stak, er mere tilbøjelig til at distrahere udviklere, der laver smarte kontrakter til TON-platformen, end at hjælpe dem.

Nix: at sætte projektet sammen

Hos Serokell er vi store fans Nix. Vi samler vores projekter hos dem og implementerer dem vha NixOps, og installeret på alle vores servere Nix OS. Takket være dette er alle vores builds reproducerbare og fungerer på ethvert operativsystem, som Nix kan installeres på.

Så vi startede med at skabe Nix overlæg med udtryk til TON montage. Med dens hjælp er kompilering af TON så enkel som muligt:

$ 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 på magisk vis gøre alt for dig, uanset om du bruger NixOS, Ubuntu eller macOS.

Programmering til TON

Den smarte kontraktkode i TON-netværket kører på TON Virtual Machine (TVM). TVM er mere kompleks end de fleste andre virtuelle maskiner, og har meget interessant funktionalitet, den kan fx arbejde med fortsættelser и links til data.

Desuden skabte fyrene fra TON tre nye programmeringssprog:

Femte er et universelt stack programmeringssprog, der ligner Forth. Hans superevne er evnen til at interagere med TVM.

FunC er et smart kontraktprogrammeringssprog, der ligner C og er kompileret til et andet sprog - Fift Assembler.

Femte Assembler — Fift-bibliotek til generering af binær eksekverbar kode til TVM. Fifth Assembler har ikke en compiler. Det her Embedded Domain Specific Language (eDSL).

Vores konkurrence virker

Endelig er det tid til at se på resultaterne af vores indsats.

Asynkron betalingskanal

Betalingskanal er en smart kontrakt, der giver to brugere mulighed for at sende betalinger uden for blockchain. Som et resultat sparer du ikke kun penge (der er ingen kommission), men også tid (du behøver ikke vente på, at den næste blok skal behandles). Betalinger kan være så små som ønsket og så ofte som nødvendigt. I dette tilfælde behøver parterne ikke at stole på hinanden, da retfærdigheden af ​​den endelige afregning er garanteret af den smarte kontrakt.

Vi fandt en ret simpel løsning på problemet. To parter kan udveksle underskrevne beskeder, der hver indeholder to numre - det fulde beløb, som hver part betaler. Disse to tal fungerer som vektor ur i traditionelle distribuerede systemer og sæt "sket før"-rækkefølgen på transaktioner. Ved at bruge disse data vil kontrakten være i stand til at løse enhver mulig konflikt.

Faktisk er ét tal nok til at implementere denne idé, men vi forlod begge, fordi vi på denne måde kunne lave en mere bekvem brugergrænseflade. Derudover besluttede vi at inkludere betalingsbeløbet i hver besked. Uden det, hvis beskeden går tabt af en eller anden grund, så vil brugeren muligvis ikke bemærke tabet, selvom alle beløb og den endelige beregning vil være korrekte.

For at teste vores idé ledte vi efter eksempler på at bruge en så enkel og kortfattet betalingskanalprotokol. Overraskende nok fandt vi kun to:

  1. beskrivelse en lignende tilgang, kun i tilfælde af en ensrettet kanal.
  2. Tutorial, som beskriver den samme idé som vores, men uden at forklare mange vigtige detaljer, såsom generel korrekthed og konfliktløsningsprocedurer.

Det blev klart, at det giver mening at beskrive vores protokol i detaljer, med særlig opmærksomhed på dens korrekthed. Efter flere gentagelser var specifikationen klar, og nu kan du også. kig på hende.

Vi implementerede kontrakten i FunC, og vi skrev kommandolinjeværktøjet til at interagere med vores kontrakt helt i Fift, som anbefalet af arrangørerne. Vi kunne have valgt et hvilket som helst andet sprog til vores CLI, men vi var interesserede i at prøve Fit for at se, hvordan det fungerede i praksis.

For at være ærlig, efter at have arbejdet med Fift, så vi ikke nogen tvingende grunde til at foretrække dette sprog frem for populære og aktivt brugte sprog med udviklede værktøjer og biblioteker. Programmering i et stack-baseret sprog er ret ubehageligt, da man hele tiden skal have i hovedet, hvad der er på stakken, og det hjælper compileren ikke med.

Derfor, efter vores mening, er den eneste begrundelse for eksistensen af ​​Fift dets rolle som værtssprog for Fift Assembler. Men ville det ikke være bedre at integrere TVM-samleren i et eksisterende sprog i stedet for at opfinde et nyt til dette i det væsentlige eneste formål?

TVM Haskell eDSL

Nu er det tid til at tale om vores anden smarte kontrakt. Vi besluttede at udvikle en tegnebog med flere signaturer, men at skrive endnu en smart kontrakt i FunC ville være for kedeligt. Vi ville gerne tilføje noget smag, og det var vores eget samlesprog for TVM.

Ligesom Fift Assembler er vores nye sprog indlejret, men vi valgte Haskell som vært i stedet for Fift, hvilket giver os mulighed for at drage fuld fordel af dets avancerede typesystem. Når man arbejder med smarte kontrakter, hvor omkostningerne ved selv en lille fejl kan være meget høje, er statisk skrivning efter vores mening en stor fordel.

For at demonstrere, hvordan TVM assembler ser ud indlejret i Haskell, implementerede vi en standard tegnebog på den. Her er et par ting at 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ærtssproget (dvs. Haskell), giver vores eDSL dig mulighed for at vælge, om du vil have den til at blive en separat rutine i TVM eller blot indlejret ved opkaldspunktet.
  • Ligesom Haskell har funktioner typer, der kontrolleres på kompileringstidspunktet. I vores eDSL er inputtypen for en funktion den type stak, som funktionen forventer, og resultattypen er den type stak, der vil blive produceret efter opkaldet.
  • Koden har anmærkninger stacktype, der beskriver den forventede staktype ved opkaldspunktet. I den oprindelige tegnebogskontrakt var disse kun kommentarer, men i vores eDSL er de faktisk en del af koden og kontrolleres på kompileringstidspunktet. De kan tjene som dokumentation eller erklæringer, der hjælper udvikleren med at finde problemet, hvis koden ændres, og staktypen ændres. Sådanne annoteringer påvirker naturligvis ikke runtime-ydelsen, da der ikke genereres TVM-kode til dem.
  • Dette er stadig en prototype skrevet på to uger, så der er stadig meget arbejde at gøre på projektet. For eksempel bør alle forekomster af de klasser, du ser i koden nedenfor, genereres automatisk.

Sådan ser implementeringen af ​​en multisig tegnebog ud på 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 multi-signatur tegnebogskontrakt kan findes på dette depot. Og mere fortalt i detaljer om indbyggede sprog, vores kollega Georgy Agapov.

Konklusioner om konkurrencen og TON

I alt tog vores arbejde 380 timer (inklusive fortrolighed med dokumentation, møder og faktisk udvikling). Fem udviklere deltog i konkurrenceprojektet: CTO, teamleder, blockchain-platformspecialister og Haskell-softwareudviklere.

Vi fandt ressourcer til at deltage i konkurrencen uden problemer, da ånden i et hackathon, tæt teamwork og behovet for hurtigt at fordybe os i aspekter af nye teknologier altid er spændende. Adskillige søvnløse nætter for at opnå maksimale resultater under forhold med begrænsede ressourcer kompenseres af uvurderlig erfaring og fremragende minder. Derudover er arbejdet med sådanne opgaver altid en god test af virksomhedens processer, da det er ekstremt svært at opnå virkelig anstændige resultater uden velfungerende intern interaktion.

Sangtekster til side: vi var imponerede over mængden af ​​arbejde, som TON-teamet har lagt ned. Det lykkedes dem at bygge et komplekst, smukt og vigtigst af alt fungerende system. TON har bevist sig selv som en platform med stort potentiale. Men for at dette økosystem kan udvikle sig, skal der gøres meget mere, både med hensyn til dets brug i blockchain-projekter og med hensyn til at forbedre udviklingsværktøjer. Vi er stolte over nu at være en del af denne proces.

Hvis du efter at have læst denne artikel stadig har spørgsmål eller ideer til, hvordan du bruger TON til at løse dine problemer, skriv til os — vi deler gerne ud af vores erfaringer.

Kilde: www.habr.com

Tilføj en kommentar