Cu siguranță ați auzit că Telegram Dar s-ar putea să fi pierdut vestea că, nu cu mult timp în urmă, Telegram pentru realizarea unuia sau mai multor contracte inteligente pentru această platformă.
Echipa Serokell, cu o vastă experiență în dezvoltarea proiectelor mari de blockchain, nu putea rămâne indiferentă. Am delegat cinci angajați la concurs, iar după doar două săptămâni au câștigat locul întâi sub (ne)modestul nickname Sexy Chameleon. În acest articol, vă voi povesti cum au reușit. Sperăm că, în următoarele zece minute, veți citi cel puțin o poveste interesantă, iar, în cel mai bun caz, veți găsi în ea ceva util pe care să-l aplicați în munca dumneavoastră.
Dar să începem cu o scurtă introducere în context.
Concursul și condițiile acestuia
Așadar, principalele sarcini ale participanților au fost realizarea unuia sau mai multor contracte inteligente propuse, precum și formularea de sugestii pentru îmbunătățirea ecosistemului TON. Concursul a avut loc între 24 septembrie și 15 octombrie, iar rezultatele au fost anunțate abia pe 15 noiembrie. Destul de lung, având în vedere că, între timp, Telegram a reușit să desfășoare și să anunțe rezultatele concursurilor de design și de dezvoltare a aplicațiilor în C++ pentru testarea și evaluarea calității apelurilor VoIP în Telegram.
Am ales din lista propusă de organizatori două contracte inteligente. Pentru unul dintre ele am folosit instrumentele furnizate împreună cu TON, iar al doilea l-am implementat într-un nou limbaj, dezvoltat de inginerii noștri special pentru TON și integrat în Haskell.
Alegerea unui limbaj de programare funcțional nu este întâmplătoare. În blogul nostru adesea discutăm despre de ce considerăm că complexitatea limbajelor funcționale este o exagerare și de ce, în general, le preferăm pe cele orientate obiect. Apropo, aici găsiți și .
De ce am decis să participăm de fapt
Pe scurt, pentru că specializarea noastră sunt proiectele neconvenționale și complexe, care necesită abilități speciale și care, adesea, au o valoare științifică pentru comunitatea IT. Susținem cu pasiune dezvoltarea open-source și ne implicăm în popularizarea acesteia, și colaborează cu cele mai importante universități din Rusia în domeniul științelor informației și matematicii.
Provocările interesante ale concursului și implicarea în proiectul nostru iubit, Telegram, au fost o motivație excelentă, iar premiul a reprezentat un stimulent suplimentar. 🙂
Cercetarea blockchain-ului TON
Urmărim cu atenție noile dezvoltări în blockchain, inteligența artificială și învățarea automată, încercând să nu ratăm nicio lansare semnificativă din fiecare domeniu în care activăm. Așadar, cu ocazia începutului concursului, echipa noastră era deja familiarizată cu ideile din . Totuși, înainte de a începe să lucrăm cu TON, nu am analizat documentația tehnică și codul sursă efectiv al platformei, așa că primul pas a fost destul de evident — o cercetare atentă a documentației oficiale de pe și în .
La începutul concursului, codul era deja publicat, așa că, pentru a economisi timp, am decis să căutăm un ghid sau un rezumat scris de utilizatori. Din păcate, acest lucru nu a adus rezultate — în afară de instrucțiuni pentru construirea platformei pe Ubuntu, nu am găsit alte materiale.
Documentația în sine s-a dovedit a fi bine structurată, dar a fost uneori dificil de citit. Destul de des a trebuit să ne întoarcem la anumite puncte și să trecem de la descrierile de nivel înalt ale ideilor abstracte la detalii de nivel scăzut ale implementării.
Ar fi fost mai simplu dacă specificația nu ar fi avut deloc o descriere detaliată a implementării. Informația despre modul în care mașina virtuală își reprezintă stiva mai mult îi distrage pe dezvoltatorii care creează smart contracts pentru platforma TON, decât să îi ajute.
Nix: construim proiectul
La Serokell, suntem mari fani . Ne construim proiectele folosind , iar pe toate serverele noastre este instalat . Datorită acestui lucru, toate construcțiile noastre sunt reproducibile și funcționează pe orice sistem de operare pe care poate fi instalat Nix.
Așadar, am început cu crearea . Cu ajutorul acestuia, compilarea TON este extrem de simplă:
$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && makeRețineți că nu trebuie să instalați nicio dependență. Nix va face totul magic pentru dumneavoastră, indiferent dacă utilizați NixOS, Ubuntu sau macOS.
Programarea pentru TON
Codul contractelor inteligente în TON Network este executat pe TON Virtual Machine (TVM). TVM este mai complexă decât majoritatea celorlalte mașini virtuale și oferă funcționalități interesante, cum ar fi capacitatea de a lucra cu continuări (continuations) și linkuri către date.
Mai mult, echipa de la TON a creat nu mai puțin de trei limbaje de programare noi:
Fift — un limbaj de programare stivuit universal, asemănător cu . Abilitatea sa superioară este capacitatea de a interacționa cu TVM.
FunC — un limbaj de programare pentru contracte inteligente, care este similar cu și se compilează într-un alt limbaj — Fift Assembler.
Fift Assembler — o bibliotecă Fift pentru generarea de cod executabil binar pentru TVM. Fift Assembler nu dispune de un compilator. Este .
Lucrările noastre de concurs
În sfârșit, a venit timpul să ne uităm la rezultatele eforturilor noastre.
Canal de plată asincron
Canalul de plată (payment channel) este un contract inteligent care permite doi utilizatori să trimită plăți în afara blockchain-ului. Ca rezultat, se economisesc nu doar bani (nu există comision), ci și timp (nu trebuie să aștepți procesarea unui nou bloc). Plățile pot fi orice sumă mică și se pot face atât de des cât este necesar. De asemenea, părțile nu trebuie să se încreadă una în cealaltă, deoarece corectitudinea plății finale este garantată de contractul inteligent.
Am găsit o soluție destul de simplă la problemă. Cele două părți pot schimba mesaje semnate, fiecare dintre acestea conținând două numere — suma totală plătită de fiecare dintre participanți. Aceste două numere funcționează ca în sistemele distribuite tradiționale și stabilesc ordinea „a avut loc înainte” asupra tranzacțiilor. Folosind aceste date, contractul va putea rezolva orice conflict posibil.
În realitate, pentru implementarea acestei idei este suficient un singur număr, dar am lăsat ambele, deoarece astfel am putut crea o interfață mai convenabilă pentru utilizatori. În plus, am decis să includem în fiecare mesaj și dimensiunea plății. Fără aceasta, dacă mesajul se pierde din anumite motive, deși toate sumele și calculul final vor fi corecte, utilizatorul ar putea să nu observe pierderea.
Pentru a verifica ideea noastră, am căutat exemple de utilizare a unui astfel de protocol simplu și concis de canal de plată. Spre surprinderea noastră, am găsit doar două:
- o abordare similară, doar pentru un canal unidirecțional.
- , care descrie aceeași idee ca a noastră, dar fără explicația multor detalii importante, cum ar fi corectitudinea generală și procedura de rezolvare a conflictelor.
A devenit clar că are sens să descriem în detaliu protocolul nostru, acordând o atenție deosebită corectitudinii sale. După câteva iterații, specificația a fost finalizată, iar acum o puteți .
Am implementat un contract pe FunC, iar utilitarul de linie de comandă pentru interacțiunea cu contractul nostru l-am scris complet în Fift, așa cum au recomandat organizatorii. Am fi putut alege orice altă limbă pentru CLI-ul nostru, dar am fost curioși să încercăm Fift pentru a vedea cum se comportă în practică.
Sincer, după ce am lucrat cu Fift, nu am văzut motive convingătoare de a prefera această limbă în fața limbilor populare și larg utilizate cu un ecosistem bine dezvoltat de instrumente și biblioteci. Programarea într-o limbă bazată pe stivă este destul de neplăcută, deoarece trebuie să ții mereu minte ce se află unde în stivă, iar compilatorul nu ajută cu nimic în acest sens.
De aceea, singura justificare pe care o vedem pentru existența Fift este rolul său ca limbaj gazdă pentru Fift Assembler. Dar nu ar fi fost mai bine să integrăm assemblerul TVM într-o limbă existentă, decât să inventăm una nouă pentru acest scop, care este, în esență, singurul?
TVM Haskell eDSL
Acum este timpul să vorbim despre al doilea nostru smart contract. Am decis să dezvoltăm un portofel cu multiple semnături, dar să scriem încă un smart contract pe FunC ar fi fost prea plictisitor. Ne-am dorit să adăugăm un pic de originalitate, iar aceasta a fost noua noastră limbă de asamblare pentru TVM.
Ca și Fift Assembler, noua noastră limbă este integrabilă, doar că, în loc de Fift, am ales Haskell ca gazdă, ceea ce ne-a permis să beneficiem pe deplin de sistemul său avansat de tipuri. Când lucrăm cu smart contracte, unde prețul unei greșeli minore poate fi foarte mare, tipizarea statică, în opinia noastră, este un mare avantaj.
Pentru a demonstra cum arată asamblatorul TVM, integrat în Haskell, am implementat un portofel standard. Iată câteva aspecte la care merită să fii atent:
- Acest contract constă într-o singură funcție, dar poți utiliza câte vrei. Atunci când definești o nouă funcție în limbajul gazdă (adică în Haskell), eDSL-ul nostru îți permite să alegi dacă vrei să se transforme într-o subprogramă separată în TVM sau să fie integrată direct în locul apelului.
- La fel ca în Haskell, funcțiile au tipuri care sunt verificate în timpul compilării. În eDSL-ul nostru, tipul de intrare al funcției este tipul stivei pe care funcția o așteaptă, iar tipul rezultat este tipul stivei care va rezulta după apel.
- Codul conține adnotări
stacktype, care descriu tipul așteptat al stivei la punctul de apel. În contractul original al portofelului, acestea erau doar comentarii, dar în eDSL-ul nostru ele fac parte efectiv din cod și sunt verificate în timpul compilării. Ele pot servi ca documentație sau afirmații care ajută dezvoltatorul să identifice o problemă în cazul în care tipul stivei se va schimba datorită modificărilor în cod. Desigur, astfel de adnotări nu afectează performanța în timpul execuției, deoarece nu este generat niciun cod TVM pentru ele. - Acesta este încă un prototip, scris în două săptămâni, așa că proiectul necesită încă multă muncă. De exemplu, toate instanțele claselor pe care le vezi în codul de mai jos trebuie generate automat.
Iată cum arată implementarea portofelului multisig în eDSL-ul nostru:
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 @Word32Codul sursă complet al eDSL-ului nostru și contractul portofelului cu semnături multiple pot fi găsite în Un mai despre limbajele integrate colegul nostru Georgii Agapov.
Concluziile despre competiție și TON
În total, munca noastră a durat 380 de ore (inclusiv familiarizarea cu documentația, întâlnirile și dezvoltarea propriu-zisă). La proiectul competițional au participat cinci dezvoltatori: CTO, team lead, specialiști în platforme blockchain și dezvoltatori de software în Haskell.
Resursele pentru a participa la concurs le-am găsit fără dificultate, deoarece spiritul hackathon-ului, munca în echipă strânsă și necesitatea de a ne adapta rapid la aspectele noilor tehnologii sunt întotdeauna captivante. Câteva nopți albastre în numele obținerii celor mai bune rezultate în condiții de resurse limitate sunt compensate de experiența inestimabilă și amintirile excelente. În plus, lucrul la astfel de sarcini reprezintă întotdeauna o bună verificare a proceselor companiei, deoarece obținerea unor rezultate cu adevărat demne fără o interacțiune internă bine pusă la punct este extrem de dificilă.
În afară de lirică: am fost impresionați de volumul de muncă realizat de echipa TON. Au reușit să construiască un sistem complex, frumos și, cel mai important, funcțional. TON s-a dovedit a fi o platformă cu un mare potențial. Totuși, pentru ca această ecosistemă să evolueze, mai este mult de făcut, atât din perspectiva utilizării sale în proiectele blockchain, cât și din perspectiva îmbunătățirii instrumentelor de dezvoltare. Suntem mândri că acum facem parte din acest proces.
Dacă după citirea acestui articol aveți întrebări sau ați dezvoltat idei despre cum să aplicați TON pentru a rezolva sarcinile dumneavoastră, — ne face plăcere să împărtășim experiența noastră.
Sursa: habr.com
