Avrete sicuramente sentito che Telegram . Ma potreste aver perso la notizia che poco tempo fa Telegram per implementare uno o più contratti smart per questa piattaforma.
Il team di Serokell, con una vasta esperienza nello sviluppo di grandi progetti blockchain, non poteva rimanere indifferente. Abbiamo delegato cinque membri al concorso e, già dopo due settimane, hanno conquistato il primo posto con il (non)modesto nickname Sexy Chameleon. In questo articolo vi racconterò come ci sono riusciti. Speriamo che nei prossimi dieci minuti leggiate almeno una storia interessante, e al massimo troviate qualcosa di utile da applicare nel vostro lavoro.
Ma iniziamo con un breve approfondimento sul contesto.
Il concorso e le sue condizioni
Così, le principali responsabilità dei partecipanti sono state l'implementazione di uno o più dei contratti intelligenti proposti, oltre a presentare suggerimenti per migliorare l'ecosistema TON. Il concorso si è tenuto dal 24 settembre al 15 ottobre, e i risultati sono stati annunciati solo il 15 novembre. Abbastanza a lungo, considerando che in quel periodo Telegram ha già condotto e annunciato i risultati dei contest sul design e dello sviluppo di applicazioni in C++ per testare e valutare la qualità delle chiamate VoIP su Telegram.
Abbiamo scelto due contratti intelligenti dalla lista fornita dagli organizzatori. Per uno di essi abbiamo utilizzato gli strumenti forniti insieme a TON, e il secondo è stato realizzato 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 riteniamo che la complessità dei linguaggi funzionali sia una grande esagerazione e perché, in generale, li preferiamo a quelli orientati agli oggetti. A proposito, qui c'è anche .
Perché abbiamo deciso di partecipare
In breve, la nostra specializzazione è in progetti personalizzati e complessi che richiedono abilità particolari e spesso rappresentano un valore scientifico per la comunità IT. Sosteniamo con entusiasmo lo sviluppo open-source e ci impegniamo nella sua promozione, collaborando anche con le principali università della Russia nel campo delle scienze informatiche e della matematica.
Le sfide interessanti del concorso e il coinvolgimento nel progetto Telegram, che amiamo, sono stati già di per sé una grande motivazione, mentre il montepremi ha fornito un ulteriore stimolo. 🙂
Ricerca sulla blockchain TON
Seguiamo da vicino le nuove sviluppi nel campo della blockchain, dell'intelligenza artificiale e dell'apprendimento automatico, cercando di non perdere nessun rilascio significativo in ciascuna delle aree in cui operiamo. Pertanto, al momento dell'inizio del concorso, il nostro team era già a conoscenza delle idee provenienti da . Tuttavia, prima di iniziare a lavorare con TON, non avevamo analizzato la documentazione tecnica e il codice sorgente attuale della piattaforma, quindi il primo passo è stato piuttosto ovvio: un'attenta analisi della documentazione ufficiale su e nel .
All'inizio del concorso il codice era già stato pubblicato, quindi, per risparmiare tempo, abbiamo deciso di cercare una guida o un riassunto scritto dagli utenti. Sfortunatamente, non abbiamo ottenuto risultati - oltre a un'istruzione su come assemblare la piattaforma su Ubuntu, non abbiamo trovato altri materiali.
La documentazione stessa si è rivelata accuratamente sviluppata, ma in alcuni punti era difficile da leggere. Spesso ci siamo trovati a tornare su determinati argomenti e a passare da descrizioni ad alto livello di concetti astratti a dettagli di implementazione a basso livello.
Sarebbe stato più semplice se le specifiche non avessero fornito affatto una descrizione dettagliata dell'implementazione. Le informazioni su come la macchina virtuale rappresenta il proprio stack piuttosto distraggono gli sviluppatori che creano contratti intelligenti per la piattaforma TON, piuttosto che aiutarli.
Nix: stiamo assemblando il progetto
In Serokell siamo grandi fan . Assemblando i nostri progetti e distribuendoli utilizzando , tutti i nostri server sono dotati di . Grazie a questo, tutte le nostre build sono riproducibili e funzionano su qualsiasi sistema operativo su cui può essere installato Nix.
Pertanto, abbiamo iniziato con la creazione . Con questo, compilare TON diventa molto semplice:
$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && makeNotate che non è necessario installare alcuna dipendenza. Nix fa tutto magicamente per voi, indipendentemente dal fatto che stiate usando 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 della maggior parte delle altre macchine virtuali e offre funzionalità molto interessanti, come la capacità di 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 stack universale, simile a . La sua superpotenza è la possibilità di interagire con la TVM.
FunC — un linguaggio di programmazione per contratti intelligenti, simile a e compilato 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. Questa è .
I nostri lavori di concorso
Finalmente, è il momento di dare un'occhiata ai 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, non solo si risparmiano soldi (non ci sono commissioni), ma anche tempo (non è necessario attendere l'elaborazione di un blocco). I pagamenti possono essere di qualsiasi importo e avvenire con la frequenza necessaria. Inoltre, le parti non devono necessariamente fidarsi l'una dell'altra, poiché l'equità del regolamento finale è garantita dal contratto intelligente.
Abbiamo trovato una soluzione abbastanza semplice al problema. Le due parti possono scambiarsi messaggi firmati, ognuno dei quali contiene due numeri: l'importo totale versato da ciascun partecipante. Questi due numeri funzionano come nelle tradizionali sistemi distribuiti e stabiliscono l'ordine 'è avvenuto prima' su transazioni. Utilizzando questi dati, il contratto potrà risolvere qualsiasi potenziale conflitto.
In realtà, per realizzare questa idea è sufficiente un solo numero, ma abbiamo voluto mantenerne entrambi, poiché ciò ci ha permesso di creare un'interfaccia utente più comoda. Inoltre, abbiamo deciso di includere in ogni messaggio l'importo del pagamento. Senza di esso, se il messaggio per qualche motivo dovesse andare perso, anche se tutte le somme e il conteggio finale sono corretti, l'utente potrebbe non accorgersi della perdita.
Per testare la nostra idea, abbiamo cercato esempi di utilizzo di un protocollo così semplice e conciso. Con nostra sorpresa, ne abbiamo trovati solo due:
- un approccio simile, ma per un canale unidirezionale.
- , che descrive la stessa idea della nostra, ma senza spiegare molti dettagli importanti, come la correttezza complessiva e la procedura di risoluzione dei conflitti.
È diventato chiaro che ha senso descrivere dettagliatamente il nostro protocollo, prestando particolare attenzione alla sua correttezza. Dopo diverse iterazioni, la specifica è stata completata e ora anche voi potete .
Abbiamo implementato un contratto su FunC e l'utilità della riga di comando per interagire con il nostro contratto è stata completamente scritta in Fift, come raccomandato dagli organizzatori. Avremmo potuto scegliere qualsiasi altra lingua per il nostro CLI, ma siamo stati curiosi di provare proprio Fift per vedere come si sarebbe comportato in azione.
A dire il vero, dopo aver lavorato con Fift, non abbiamo visto motivi validi per preferire questo linguaggio a linguaggi più popolari e utilizzati, con strumenti e librerie più sviluppati. Programmare in un linguaggio a stack è piuttosto scomodo, poiché bisogna continuamente tenere a mente dove si trova ogni cosa nello stack, e il compilatore non aiuta affatto in questo.
Perciò, l'unica giustificazione che vediamo per l'esistenza di Fift è il suo ruolo come linguaggio ospite per l'Assemblatore Fift. Ma non sarebbe stato meglio integrare l'assemblatore TVM in un linguaggio esistente piuttosto che inventarne uno nuovo per questa, fondamentalmente, unica finalità?
TVM Haskell eDSL
È ora di parlare del nostro secondo smart contract. Abbiamo deciso di sviluppare un wallet con firma multipla, ma scrivere un altro smart contract in FunC sarebbe stato troppo noioso. Volevamo aggiungere un tocco speciale e questo è diventato il nostro linguaggio assembler personale per TVM.
Proprio come Fift Assembler, il nostro nuovo linguaggio è incorporato, ma invece di Fift abbiamo scelto Haskell come host, il che ci ha permesso di sfruttare appieno il suo avanzato sistema di tipi. Quando si lavora con smart contract, dove il costo anche di un piccolo errore può essere molto elevato, la tipizzazione statica è, a nostro avviso, un grande vantaggio.
Per dimostrare come appare l'assembler TVM integrato in Haskell, abbiamo implementato un wallet standard. Ecco alcune cose su cui vale la pena prestare attenzione:
- Questo contratto è composto da una sola funzione, ma puoi utilizzare quante ne vuoi. Quando definisci una nuova funzione nel linguaggio host (cioè in Haskell), il nostro eDSL ti consente di scegliere se desideri che diventi una sottoprogramma separato in TVM o semplicemente sia incorporata nel punto di chiamata.
- Come in Haskell, anche le funzioni hanno tipi che vengono verificati durante la compilazione. Nel nostro eDSL, il tipo di input di una 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 portafoglio erano semplici commenti, ma nel nostro eDSL fanno effettivamente parte del codice e vengono verificati durante la compilazione. Possono fungere da documentazione o affermazioni che aiutano lo sviluppatore a trovare un problema nel caso in cui il tipo di stack cambi durante le modifiche al codice. Naturalmente, tali annotazioni non influenzano le performance durante l'esecuzione, poiché non viene generato alcun codice TVM per esse. - È ancora un prototipo, scritto in due sole settimane, quindi c'è ancora molto lavoro da fare sul progetto. Ad esempio, tutte le istanze delle classi che vedete nel codice sottostante dovrebbero essere generate automaticamente.
Ecco a che punto siamo con l'implementazione del portafoglio 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 il contratto del portafoglio con firma multipla possono essere trovati in Un approfondimento Conclusioni sul concorso e TON
In totale, il nostro lavoro ha richiesto 380 ore (compreso il tempo per familiarizzare con la documentazione, le riunioni e lo sviluppo vero e proprio). Cinque sviluppatori hanno partecipato al progetto: il CTO, il team leader, specialisti in piattaforme blockchain e sviluppatori software in Haskell.
Abbiamo trovato senza difficoltà le risorse per partecipare al contest, poiché lo spirito dell'hackathon, il lavoro di squadra intenso e la necessità di un rapido approccio a nuove tecnologie sono sempre entusiasmanti. Alcune notti insonni per raggiungere il risultato massimo in condizioni di risorse limitate sono compensate da preziose esperienze e ottimi ricordi. Inoltre, lavorare su compiti di questo tipo è sempre una buona verifica dei processi aziendali, poiché ottenere risultati veramente validi senza un'interazione interna ben collaudata è estremamente difficile.
Le risorse per partecipare al contest sono state facilmente trovate, poiché lo spirito dell'hackathon, il lavoro di squadra stretto e la necessità di immergersi rapidamente in nuovi aspetti tecnologici sono sempre stimolanti. Diverse notti insonni per raggiungere il massimo risultato in condizioni di risorse limitate sono compensate da un'esperienza preziosa e ottimi ricordi. Inoltre, lavorare su compiti simili è sempre una buona verifica dei processi dell'azienda, poiché ottenere risultati veramente significativi senza una perfetta interazione interna è estremamente difficile.
Oltre alla lirica: siamo rimasti colpiti dalla quantità di lavoro svolta dal team TON. Sono riusciti a costruire un sistema complesso, bello e soprattutto funzionante. TON si è dimostrato una piattaforma con un grande potenziale. Tuttavia, per far sì che questo ecosistema cresca, c'è ancora molto da fare, sia in termini di utilizzo nei progetti blockchain sia in termini di perfezionamento degli strumenti di sviluppo. Siamo orgogliosi di far parte di questo processo.
Se dopo aver letto questo articolo avete domande o idee su come applicare TON alle vostre necessità, — saremo felici di condividere la nostra esperienza.
Fonte: habr.com
