Shndërrimi i FunC në Funksional me Haskell: Si Serokell fitoi Konkurrencën e Blockchain Telegram

Me siguri e keni dëgjuar atë Telegram është gati të lançojë platformën Ton blockchain. Por mund të keni humbur lajmin që jo shumë kohë më parë Telegram shpalli konkurs për zbatimin e një ose më shumë kontratave smart për këtë platformë.

Ekipi Serokell, me përvojë të gjerë në zhvillimin e projekteve të mëdha blockchain, nuk mund të qëndronte mënjanë. Ne deleguam pesë punonjës në konkurs dhe dy javë më vonë ata zunë vendin e parë në të me pseudonimin (jo)modest të rastësishëm Sexy Chameleon. Në këtë artikull do të flas se si ata e bënë atë. Shpresojmë që në dhjetë minutat e ardhshme të paktën të lexoni një histori interesante dhe më së shumti të gjeni diçka të dobishme në të që mund ta aplikoni në punën tuaj.

Por le të fillojmë me një kontekst të vogël.

Konkurrenca dhe kushtet e saj

Pra, detyrat kryesore të pjesëmarrësve ishin zbatimi i një ose më shumë prej kontratave smart të propozuara, si dhe bërja e propozimeve për përmirësimin e ekosistemit TON. Konkursi u zhvillua nga 24 shtatori deri më 15 tetor dhe rezultatet u shpallën vetëm më 15 nëntor. Një kohë mjaft e gjatë, duke pasur parasysh se gjatë kësaj kohe Telegram arriti të mbajë dhe shpallë rezultatet e konkurseve për hartimin dhe zhvillimin e aplikacioneve në C++ për testimin dhe vlerësimin e cilësisë së thirrjeve VoIP në Telegram.

Ne zgjodhëm dy kontrata smart nga lista e propozuar nga organizatorët. Për njërën prej tyre, ne përdorëm mjete të shpërndara me TON, dhe e dyta u zbatua në një gjuhë të re të zhvilluar nga inxhinierët tanë posaçërisht për TON dhe të integruar në Haskell.

Zgjedhja e një gjuhe programimi funksionale nuk është e rastësishme. Në tonë blog i korporatës Shpesh flasim pse mendojmë se kompleksiteti i gjuhëve funksionale është një ekzagjerim i madh dhe pse në përgjithësi i preferojmë ato ndaj atyre të orientuara nga objekti. Nga rruga, ai gjithashtu përmban origjinal i këtij artikulli.

Pse vendosëm të merrnim pjesë?

Me pak fjalë, sepse specializimi ynë është projekte jo standarde dhe komplekse që kërkojnë aftësi të veçanta dhe shpesh kanë vlerë shkencore për komunitetin e IT. Ne mbështesim fuqishëm zhvillimin e burimeve të hapura dhe jemi të angazhuar në popullarizimin e tij, si dhe bashkëpunojmë me universitetet kryesore ruse në fushën e shkencave kompjuterike dhe matematikës.

Detyrat interesante të konkursit dhe përfshirja në projektin tonë të dashur Telegram ishin në vetvete një motivim i shkëlqyer, por fondi i çmimeve u bë një nxitje shtesë. 🙂

Hulumtimi i blockchain TON

Ne monitorojmë nga afër zhvillimet e reja në blockchain, inteligjencën artificiale dhe mësimin e makinerive dhe përpiqemi të mos humbasim një publikim të vetëm të rëndësishëm në secilën prej fushave në të cilat punojmë. Prandaj, në kohën kur filloi konkursi, ekipi ynë ishte tashmë i njohur me idetë nga TON letër e bardhë. Sidoqoftë, përpara se të fillonim punën me TON, ne nuk analizuam dokumentacionin teknik dhe kodin burimor aktual të platformës, kështu që hapi i parë ishte mjaft i dukshëm - një studim i plotë i dokumentacionit zyrtar mbi Online dhe depot e projekteve.

Në kohën kur filloi konkursi, kodi ishte publikuar tashmë, kështu që për të kursyer kohë, vendosëm të kërkonim një udhëzues ose përmbledhje të shkruar nga nga përdoruesit. Fatkeqësisht, kjo nuk dha asnjë rezultat - përveç udhëzimeve për montimin e platformës në Ubuntu, nuk gjetëm materiale të tjera.

Vetë dokumentacioni ishte i mirë-hulumtuar, por ishte i vështirë për t'u lexuar në disa fusha. Shumë shpesh na duhej të ktheheshim në pika të caktuara dhe të kalonim nga përshkrimet e nivelit të lartë të ideve abstrakte në detaje të zbatimit të nivelit të ulët.

Do të ishte më e lehtë nëse specifikimi nuk do të përfshinte fare një përshkrim të detajuar të zbatimit. Informacioni rreth mënyrës sesi një makinë virtuale përfaqëson pirgun e saj ka më shumë gjasa të shpërqendrojë zhvilluesit që krijojnë kontrata inteligjente për platformën TON sesa t'i ndihmojë ata.

Nix: bashkimi i projektit

Në Serokell ne jemi fansa të mëdhenj hiç. Ne mbledhim projektet tona me të dhe i vendosim duke i përdorur NixOps, dhe të instaluar në të gjithë serverët tanë Nix OS. Falë kësaj, të gjitha ndërtimet tona janë të riprodhueshme dhe funksionojnë në çdo sistem operativ në të cilin mund të instalohet Nix.

Kështu filluam duke krijuar Mbivendosja Nix me shprehje për montimin TON. Me ndihmën e tij, përpilimi i TON është aq i thjeshtë sa të jetë e mundur:

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

Vini re se nuk keni nevojë të instaloni ndonjë varësi. Nix në mënyrë magjike do të bëjë gjithçka për ju, pavarësisht nëse jeni duke përdorur NixOS, Ubuntu ose macOS.

Programimi për TON

Kodi i kontratës inteligjente në rrjetin TON funksionon në makinën virtuale TON (TVM). TVM është më kompleks se shumica e makinave të tjera virtuale dhe ka funksionalitet shumë interesant, për shembull, mund të punojë me të vazhdime и lidhjet me të dhënat.

Për më tepër, djemtë nga TON krijuan tre gjuhë të reja programimi:

Pesë është një gjuhë programimi universale stack që i ngjan përpara. Super aftësia e tij është aftësia për të bashkëvepruar me TVM.

FunC është një gjuhë programimi e zgjuar e kontratës që është e ngjashme me C dhe është përpiluar në një gjuhë tjetër - Fift Assembler.

Asamblisti i pestë — Biblioteka e pestë për gjenerimin e kodit binar të ekzekutueshëm për TVM. Fifth Assembler nuk ka një përpilues. Kjo Gjuha specifike e domenit të integruar (eDSL).

Konkursi ynë funksionon

Më në fund, është koha për të parë rezultatet e përpjekjeve tona.

Kanali i pagesave asinkron

Kanali i pagesave është një kontratë inteligjente që lejon dy përdorues të dërgojnë pagesa jashtë blockchain. Si rezultat, ju kurseni jo vetëm para (nuk ka komision), por edhe kohë (nuk duhet të prisni që blloku tjetër të përpunohet). Pagesat mund të jenë aq të vogla sa të dëshirohet dhe aq shpesh sa kërkohet. Në këtë rast, palët nuk duhet t'i besojnë njëra-tjetrës, pasi drejtësia e zgjidhjes përfundimtare garantohet nga kontrata smart.

Ne gjetëm një zgjidhje mjaft të thjeshtë për problemin. Dy palë mund të shkëmbejnë mesazhe të nënshkruara, ku secila përmban dy numra - shumën e plotë të paguar nga secila palë. Këta dy numra funksionojnë si ora vektoriale në sistemet tradicionale të shpërndara dhe vendosni urdhrin "e ndodhur më parë" për transaksionet. Duke përdorur këto të dhëna, kontrata do të jetë në gjendje të zgjidhë çdo konflikt të mundshëm.

Në fakt, një numër mjafton për të zbatuar këtë ide, por ne i lamë të dyja sepse në këtë mënyrë mund të bënim një ndërfaqe përdoruesi më të përshtatshme. Përveç kësaj, ne vendosëm të përfshijmë shumën e pagesës në çdo mesazh. Pa të, nëse mesazhi humbet për ndonjë arsye, atëherë, megjithëse të gjitha shumat dhe llogaritja përfundimtare do të jenë të sakta, përdoruesi mund të mos e vërejë humbjen.

Për të testuar idenë tonë, ne kërkuam shembuj të përdorimit të një protokolli kaq të thjeshtë dhe konciz të kanalit të pagesave. Çuditërisht, ne gjetëm vetëm dy:

  1. Përshkrim një qasje e ngjashme, vetëm për rastin e një kanali me një drejtim.
  2. Tutorial, e cila përshkruan të njëjtën ide si e jona, por pa shpjeguar shumë detaje të rëndësishme, si korrektësia e përgjithshme dhe procedurat e zgjidhjes së konflikteve.

U bë e qartë se ka kuptim të përshkruajmë protokollin tonë në detaje, duke i kushtuar vëmendje të veçantë korrektësisë së tij. Pas disa përsëritjesh, specifikimi ishte gati, dhe tani mundeni edhe ju. shikoje atë.

Ne e zbatuam kontratën në FunC dhe shkruam programin e linjës së komandës për të bashkëvepruar me kontratën tonë tërësisht në Fift, siç rekomandohet nga organizatorët. Mund të kishim zgjedhur ndonjë gjuhë tjetër për CLI-në tonë, por ishim të interesuar të provonim Fit për të parë se si funksiononte në praktikë.

Për të qenë i sinqertë, pas punës me Fift, ne nuk pamë ndonjë arsye bindëse për të preferuar këtë gjuhë ndaj gjuhëve të njohura dhe të përdorura në mënyrë aktive me mjete dhe biblioteka të zhvilluara. Programimi në një gjuhë të bazuar në pirg është mjaft i pakëndshëm, pasi duhet të mbani vazhdimisht në kokë atë që është në pirg, dhe përpiluesi nuk ju ndihmon me këtë.

Prandaj, sipas mendimit tonë, i vetmi justifikim për ekzistencën e Fift është roli i tij si gjuhë pritëse për Fift Assembler. Por a nuk do të ishte më mirë të futej asembleri TVM në një gjuhë ekzistuese, në vend që të shpikesh një të re për këtë qëllim në thelb të vetëm?

TVM Haskell eDSL

Tani është koha të flasim për kontratën tonë të dytë inteligjente. Ne vendosëm të zhvillonim një portofol me shumë nënshkrime, por shkrimi i një kontrate tjetër inteligjente në FunC do të ishte shumë i mërzitshëm. Ne donim të shtonim pak shije, dhe kjo ishte gjuha jonë e asamblesë për TVM.

Ashtu si Fift Assembler, gjuha jonë e re është e integruar, por ne zgjodhëm Haskell si host në vend të Fift, duke na lejuar të përfitojmë plotësisht nga sistemi i tij i tipit të avancuar. Kur punoni me kontrata inteligjente, ku kostoja edhe e një gabimi të vogël mund të jetë shumë e lartë, shtypja statike, për mendimin tonë, është një avantazh i madh.

Për të demonstruar se si duket montuesi TVM i ngulitur në Haskell, ne vendosëm një portofol standard në të. Këtu janë disa gjëra për t'u kushtuar vëmendje:

  • Kjo kontratë përbëhet nga një funksion, por ju mund të përdorni sa të doni. Kur përcaktoni një funksion të ri në gjuhën pritës (d.m.th. Haskell), eDSL-ja jonë ju lejon të zgjidhni nëse dëshironi që ai të bëhet një rutinë më vete në TVM ose thjesht të inlinohet në pikën e thirrjes.
  • Ashtu si Haskell, funksionet kanë lloje që kontrollohen në kohën e përpilimit. Në eDSL-në tonë, lloji i hyrjes së një funksioni është lloji i pirgut që funksioni pret, dhe lloji i rezultatit është lloji i pirgut që do të prodhohet pas thirrjes.
  • Kodi ka shënime stacktype, duke përshkruar llojin e pritur të pirgut në pikën e thirrjes. Në kontratën origjinale të portofolit këto ishin vetëm komente, por në eDSL-në tonë ato janë në fakt pjesë e kodit dhe kontrollohen në kohën e kompilimit. Ato mund të shërbejnë si dokumentacion ose deklarata që ndihmojnë zhvilluesin të gjejë problemin nëse kodi ndryshon dhe lloji i stivës ndryshon. Natyrisht, shënime të tilla nuk ndikojnë në performancën e ekzekutimit, pasi nuk krijohet asnjë kod TVM për to.
  • Ky është ende një prototip i shkruar në dy javë, kështu që ka ende shumë punë për të bërë në projekt. Për shembull, të gjitha rastet e klasave që shihni në kodin më poshtë duhet të gjenerohen automatikisht.

Ja si duket zbatimi i një portofoli multisig në eDSL-në tonë:

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

Kodi i plotë burimor i kontratës sonë të portofolit eDSL dhe me shumë nënshkrime mund të gjendet në këtë depo. Dhe me shume thënë në detaje për gjuhët e integruara, kolegu ynë Georgy Agapov.

Konkluzione rreth konkursit dhe TON

Në total, puna jonë zgjati 380 orë (përfshirë njohjen me dokumentacionin, takimet dhe zhvillimin aktual). Pesë zhvillues morën pjesë në projektin e konkurrencës: CTO, drejtuesi i ekipit, specialistët e platformës blockchain dhe zhvilluesit e softuerit Haskell.

Ne gjetëm burime për të marrë pjesë në konkurs pa vështirësi, pasi fryma e një hackathon, puna e ngushtë në grup dhe nevoja për t'u zhytur shpejt në aspekte të teknologjive të reja është gjithmonë emocionuese. Disa netë pa gjumë për të arritur rezultate maksimale në kushte të burimeve të kufizuara kompensohen nga përvoja e paçmuar dhe kujtimet e shkëlqyera. Për më tepër, puna në detyra të tilla është gjithmonë një provë e mirë e proceseve të kompanisë, pasi është jashtëzakonisht e vështirë të arrihen rezultate vërtet të mira pa ndërveprim të brendshëm që funksionon mirë.

Teksti mënjanë: na bëri përshtypje sasia e punës së bërë nga ekipi TON. Ata arritën të ndërtonin një sistem kompleks, të bukur dhe më e rëndësishmja, funksional. TON e ka provuar veten të jetë një platformë me potencial të madh. Megjithatë, në mënyrë që ky ekosistem të zhvillohet, duhet bërë shumë më tepër, si në drejtim të përdorimit të tij në projektet blockchain, ashtu edhe në drejtim të përmirësimit të mjeteve të zhvillimit. Jemi krenarë që tani jemi pjesë e këtij procesi.

Nëse pas leximit të këtij artikulli keni ende pyetje ose keni ide se si të përdorni TON për të zgjidhur problemet tuaja, na shkruani - Ne do të jemi të lumtur të ndajmë përvojën tonë.

Burimi: www.habr.com

Shto një koment