Hai sicuramente sentito che Telegram . Ma potresti aver perso la notizia che poco tempo fa Telegram per l'implementazione di uno o più smart contract per questa piattaforma.
Il team di Serokell, con una ricca esperienza nello sviluppo di grandi progetti blockchain, non poteva rimanere a guardare. Abbiamo delegato al concorso cinque dipendenti e già dopo due settimane hanno conquistato il primo posto con il (non)modesto nickname random Sexy Chameleon. In questo articolo ti racconterò come ci sono riusciti. Speriamo che nei prossimi dieci minuti tu possa almeno leggere una storia interessante e, nel migliore dei casi, trovare qualcosa di utile da applicare nel tuo lavoro.
Ma iniziamo con un breve approfondimento del contesto.
Il concorso e le sue condizioni
Dunque, le principali task dei partecipanti erano l'implementazione di uno o più degli smart contract proposti, oltre a presentare suggerimenti per migliorare l'ecosistema TON. Il concorso si è svolto dal 24 settembre al 15 ottobre e i risultati sono stati annunciati solo il 15 novembre. Abbastanza lungo, considerando che nel frattempo Telegram ha condotto e reso pubblici i risultati dei contest sul design e sullo sviluppo di applicazioni in C++ per il test e la valutazione della qualità delle chiamate VoIP su Telegram.
Abbiamo scelto due smart contract dalla lista proposta dagli organizzatori. Per uno di essi abbiamo utilizzato strumenti forniti insieme a TON, mentre il secondo è stato implementato in un nuovo linguaggio, sviluppato dai nostri ingegneri appositamente per TON e integrato in Haskell.
La scelta del linguaggio di programmazione funzionale non è casuale. Nel nostro parliamo spesso del motivo per cui consideriamo la complessità dei linguaggi funzionali un grande esagerazione e perché in generale li preferiamo a quelli orientati agli oggetti. A proposito, lì è disponibile anche .
Perché abbiamo deciso di partecipare
In sintesi, perché la nostra specializzazione sono progetti non standard e complessi che richiedono abilità particolari e spesso rappresentano un valore scientifico per la comunità IT. Sosteniamo con fervore lo sviluppo open-source e ci dedichiamo alla sua diffusione, collaborando anche con le principali università della Russia nel campo delle scienze informatiche e della matematica.
Le interessanti sfide del concorso e il coinvolgimento nel progetto Telegram, che amiamo molto, erano già di per sé una grande motivazione, e il montepremi è stato uno stimolo aggiuntivo. 🙂
Ricerca sulla blockchain TON
Seguiamo attentamente le nuove sviluppi nel campo della blockchain, dell'intelligenza artificiale e dell'apprendimento automatico, cercando di non perdere nessun rilascio significativo in ciascuno dei settori in cui lavoriamo. Pertanto, al momento dell'inizio del concorso, il nostro team era già a conoscenza delle idee contenute in . Tuttavia, prima di iniziare a lavorare con TON, non avevamo analizzato la documentazione tecnica e il codice sorgente effettivo della piattaforma, quindi il primo passo è stato piuttosto ovvio: un'attenta ricerca della documentazione ufficiale su e in .
All'inizio del concorso, il codice era già stato pubblicato, quindi, per risparmiare tempo, abbiamo deciso di cercare una guida o un riassunto scritto da utenti. Purtroppo, questo non ha portato a risultati: oltre alle istruzioni per compilare la piattaforma su Ubuntu, non abbiamo trovato altri materiali.
La documentazione stessa si è rivelata ben dettagliata, ma in alcuni punti era difficile da leggere. Spesso ci siamo trovati a tornare su certi punti e a passare da descrizioni di alto livello di idee astratte a dettagli di basso livello sull'implementazione.
Sarebbe stato più semplice se nella specifica non ci fosse stato affatto alcun dettaglio sull'implementazione. Le informazioni su come la macchina virtuale presenta il suo stack tendono a distrarre più gli sviluppatori che creano smart contract per la piattaforma TON, piuttosto che aiutarli.
Nix: compiliamo il progetto
In Serokell siamo grandi fan di . Utilizziamo Nix per compilare i nostri progetti e distribuirli tramite , e tutti i nostri server hanno installato . Grazie a ciò, tutte le nostre build sono riproducibili e funzionano su qualsiasi sistema operativo in cui può essere installato Nix.
Quindi abbiamo iniziato creando . Con questo, compilare TON è estremamente semplice:
$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && makeSi noti che non è necessario installare alcuna dipendenza. Nix farà tutto magicamente per voi, indipendentemente dal fatto che stiate utilizzando NixOS, Ubuntu o macOS.
Programmazione per TON
Il codice dei contratti intelligenti nella TON Network viene eseguito sulla TON Virtual Machine (TVM). La TVM è più complessa rispetto alla maggior parte delle altre macchine virtuali e offre funzionalità molto interessanti, ad esempio può lavorare con continuazioni e riferimenti ai dati.
Inoltre, i ragazzi di TON hanno creato ben tre nuovi linguaggi di programmazione:
Fift — un linguaggio di programmazione a stack universale, simile a . La sua super-capacità è la possibilità di interagire con la TVM.
FunC — un linguaggio di programmazione per contratti intelligenti, simile a e si compila in un altro linguaggio — Fift Assembler.
Fift Assembler — una libreria Fift per generare codice eseguibile binario per la TVM. Fift Assembler non ha un compilatore. È .
I nostri lavori per il concorso
Finalmente, è tempo di vedere i risultati dei nostri sforzi.
Canale di pagamento asincrono
Un canale di pagamento (payment channel) è un contratto intelligente che consente a due utenti di inviare pagamenti al di fuori della blockchain. Di conseguenza, si risparmia non solo denaro (non ci sono commissioni), ma anche tempo (non è necessario attendere che venga elaborato il blocco successivo). I pagamenti possono essere di qualsiasi dimensione e avvenire così frequentemente come necessario. Inoltre, non è necessario fidarsi l'uno dell'altro, poiché la giustizia del pagamento finale è garantita dal contratto intelligente.
Abbiamo trovato una soluzione piuttosto semplice al problema. Le due parti possono scambiarsi messaggi firmati, ognuno dei quali contiene due numeri: l'importo totale pagato da ciascun partecipante. Questi due numeri funzionano come nelle tradizionali sistemi distribuiti e definiscono l'ordine 'è accaduto prima' delle transazioni. Utilizzando questi dati, il contratto potrà risolvere qualsiasi possibile conflitto.
In realtà, per implementare questa idea basta un solo numero, ma abbiamo mantenuto entrambi, poiché in questo modo siamo riusciti a creare un'interfaccia utente più comoda. Inoltre, abbiamo deciso di includere in ogni messaggio la dimensione del pagamento. Senza di essa, se un messaggio dovesse andare perso per qualche motivo, sebbene tutti gli importi e il regolamento finale siano corretti, l'utente potrebbe non accorgersi della perdita.
Per verificare la nostra idea, abbiamo cercato esempi di utilizzo di un protocollo di pagamento così semplice e conciso. Con sorpresa, abbiamo trovato solo due:
- approcci simili, ma per un canale unidirezionale.
- , che descrive la stessa idea che abbiamo, ma senza spiegare molti dettagli importanti, come la correttezza generale e la procedura di risoluzione dei conflitti.
È diventato chiaro che ha senso descrivere il nostro protocollo in dettaglio, prestando particolare attenzione alla sua correttezza. Dopo diverse iterazioni, la specifica era pronta e ora potete anche voi .
Abbiamo implementato il contratto su FunC, mentre l'utilità della riga di comando per interagire con il nostro contratto è stata completamente scritta in Fift, come consigliato dagli organizzatori. Avremmo potuto scegliere qualsiasi altro linguaggio per il nostro CLI, ma ci interessava provare proprio Fift, per vedere come si comportava.
A dire il vero, lavorando con Fift, non abbiamo visto valide ragioni per preferire questo linguaggio a linguaggi più popolari e attivamente utilizzati, dotati di strumenti e librerie sviluppati. Programmare in un linguaggio stack non è particolarmente piacevole, poiché bisogna sempre tenere a mente cosa sia dove nello stack, e il compilatore non aiuta affatto.
Pertanto, l'unica giustificazione per noi dell'esistenza di Fift è il suo ruolo come linguaggio host per Fift Assembler. Ma non sarebbe stato meglio integrare l'assemblatore TVM in qualche linguaggio esistente, piuttosto che inventarne uno nuovo per questo, in sostanza, unico scopo?
TVM Haskell eDSL
È giunto il momento di parlare del nostro secondo smart contract. Abbiamo deciso di sviluppare un portafoglio con firma multipla, ma scrivere un altro smart contract su FunC sarebbe stato troppo noioso. Volevamo aggiungere un tocco speciale, e questo è stato il nostro linguaggio di assemblaggio per TVM.
Proprio come Fift Assembler, il nostro nuovo linguaggio è integrabile, solo che come host al posto di Fift abbiamo scelto Haskell, il che ci ha permesso di sfruttare appieno il suo avanzato sistema di tipi. Lavorando con smart contract, dove il costo anche di un piccolo errore può essere molto elevato, riteniamo che la tipizzazione statica sia un grande vantaggio.
Per dimostrare come appare l'assemblatore TVM integrato in Haskell, abbiamo implementato un wallet standard. Ecco alcune cose a cui prestare attenzione:
- Questo contratto consiste in una sola funzione, ma puoi usarne quante vuoi. Quando definisci una nuova funzione nel linguaggio host (cioè in Haskell), il nostro eDSL ti consente di scegliere se desideri che diventi un sottoprogramma separato in TVM o venga semplicemente incorporata nel punto di chiamata.
- Come in Haskell, le funzioni hanno tipi che vengono verificati durante la compilazione. Nel nostro eDSL, il tipo di ingresso della funzione è il tipo di stack che la funzione si aspetta, mentre il tipo di risultato è il tipo di stack che si otterrà dopo la chiamata.
- Nel codice ci sono annotazioni
stacktype, che descrivono il tipo di stack atteso nel punto di chiamata. Nel contratto originale del wallet erano semplicemente commenti, ma nel nostro eDSL sono effettivamente parte del codice e vengono verificati durante la compilazione. Possono servire come documentazione o assert che aiutano lo sviluppatore a trovare problemi nel caso in cui, modificando il codice, il tipo di stack cambi. È scontato che tali annotazioni non influenzano le prestazioni durante l'esecuzione, poiché non viene generato alcun codice TVM per esse. - È ancora un prototipo, scritto in due settimane, quindi c'è ancora molto lavoro da fare. Ad esempio, tutte le istanze delle classi che vedi nel codice qui sotto dovrebbero essere generate automaticamente.
Ecco come appare l'implementazione di un wallet multisig nel nostro 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 @Word32Il codice sorgente completo del nostro eDSL e del contratto del portafoglio multi-firma può essere trovato in E in più sui linguaggi incorporati, il nostro collega Georgy Agapov.
Conclusioni sul concorso e TON
In totale, il nostro lavoro ha richiesto 380 ore (incluso il tempo per familiarizzare con la documentazione, riunioni e sviluppo diretto). Al progetto del concorso hanno partecipato cinque sviluppatori: CTO, team lead, specialisti di piattaforme blockchain e sviluppatori di software in Haskell.
Abbiamo trovato facilmente le risorse per partecipare al contest, poiché lo spirito dell'hackathon, il lavoro di squadra ravvicinato e la necessità di immergersi rapidamente negli aspetti delle nuove tecnologie sono sempre entusiasmanti. Diverse notti insonni dedicate a raggiungere il massimo risultato in condizioni di risorse limitate vengono compensate da un'esperienza inestimabile e ottimi ricordi. Inoltre, lavorare su compiti simili è sempre un buon test per i processi aziendali, poiché ottenere risultati veramente degni senza un'interazione interna ben collaudata è estremamente difficile.
Oltre alla lirica: siamo stati colpiti dall'enorme lavoro svolto dal team di TON. Sono riusciti a costruire un sistema complesso, bello e, soprattutto, funzionante. TON si è dimostrato una piattaforma con un grande potenziale. Tuttavia, affinché questo ecosistema si sviluppi, c'è ancora molto da fare sia dal punto di vista del suo utilizzo nei progetti blockchain, sia dal punto di vista del miglioramento degli strumenti di sviluppo. Siamo orgogliosi di far parte di questo processo.
Se dopo aver letto questo articolo avete domande o idee su come utilizzare TON per risolvere i vostri problemi, — saremo lieti di condividere la nostra esperienza.
Fonte: habr.com
