Transformer FunC en FunCtional avec Haskell: comment Serokell a remporté la compétition de blockchain de Telegram

Vous avez probablement entendu dire que Telegram allait lancer la plateforme de blockchain Ton . Mais vous auriez pu manquer la nouvelle qu'il n'y a pas si longtemps, Telegram a annoncé un concours pour la mise en œuvre d'un ou plusieurs contrats intelligents pour cette plateforme.


L'équipe Serokell avec une riche expérience dans le développement de grands projets de blockchain ne pouvait pas rester à l'écart. Nous avons délégué cinq employés au concours, et deux semaines plus tard, ils ont pris la première place sous le surnom aléatoire (un) modeste Sexy Chameleon. Dans cet article, je parlerai de la façon dont ils ont réussi. Nous espérons qu'au cours des dix prochaines minutes, vous lirez au moins une histoire intéressante et, au maximum, vous y trouverez quelque chose d'utile que vous pourrez appliquer dans votre travail.


Mais commençons par un petit plongeon dans le contexte.


La concurrence et ses conditions


Ainsi, les principales tâches des participants étaient la mise en œuvre d'un ou plusieurs des contrats intelligents proposés, ainsi que la formulation de propositions pour améliorer l'écosystème TON. Le concours s'est déroulé du 24 septembre au 15 octobre et les résultats n'ont été annoncés que le 15 novembre. Pendant longtemps, étant donné que pendant ce temps, Telegram a réussi à mener et à annoncer les résultats de concours sur la conception et le développement d'applications en C ++ pour tester et évaluer la qualité des appels VoIP dans Telegram.


Nous avons sélectionné deux smart contrats dans la liste proposée par les organisateurs. Pour l'un d'entre eux, nous avons utilisé des outils distribués avec TON, et le second nous l'avons implémenté dans un nouveau langage développé par nos ingénieurs spécifiquement pour TON et intégré à Haskell.


Le choix d'un langage de programmation fonctionnel n'est pas accidentel. Dans notre blog d'entreprise, nous expliquons souvent pourquoi nous considérons la complexité des langages fonctionnels comme une grande exagération et pourquoi nous préférons généralement qu'ils soient orientés objet. À propos, il contient également l' original de cet article .


Pourquoi avons-nous décidé de participer


En bref, parce que notre spécialisation est des projets non standard et complexes qui nécessitent des compétences particulières et sont souvent d'une valeur scientifique pour la communauté informatique. Nous soutenons chaleureusement le développement open-source et nous sommes engagés dans sa vulgarisation, ainsi que coopérons avec les principales universités en Russie dans le domaine de l'informatique et des mathématiques.


Les tâches intéressantes du concours et la participation au projet Telegram, que nous aimions tant, étaient en elles-mêmes une excellente motivation, mais le prix était devenu une incitation supplémentaire. :)


TON Blockchain Research


Nous suivons de près les nouveaux développements dans la blockchain, l'intelligence artificielle et l'apprentissage automatique et essayons de ne pas manquer une seule version significative dans chacun des domaines dans lesquels nous travaillons. Par conséquent, au début du concours, notre équipe connaissait déjà les idées du livre blanc de TON . Cependant, avant de commencer à travailler avec TON, nous n'avons pas analysé la documentation technique et le code source réel de la plate-forme, donc la première étape était assez évidente - une étude approfondie de la documentation officielle sur le site et dans le référentiel du projet .


Au début du concours, le code avait déjà été publié, donc pour gagner du temps, nous avons décidé de chercher un guide ou une compression écrite par les utilisateurs . Malheureusement, cela n'a pas donné de résultat - à part les instructions pour construire la plate-forme sur Ubuntu, nous n'avons pas trouvé d'autres matériaux.


La documentation elle-même a été soigneusement développée, mais il a été difficile de la lire à certains moments. Très souvent, nous avons dû revenir sur certains points et passer des descriptions de haut niveau des idées abstraites aux détails de mise en œuvre de bas niveau.


Ce serait plus facile si la spécification n'avait pas du tout de description détaillée de l'implémentation. Les informations sur la façon dont la machine virtuelle présente sa pile sont plus distrayantes pour les développeurs qui créent des contrats intelligents pour la plateforme TON que pour les aider.


Nix: construire un projet


Chez Serokell, nous sommes de grands fans de Nix . Nous collectons nos projets pour eux et les déployons à l' aide de NixOps , et NixOS est installé sur tous nos serveurs. Grâce à cela, toutes nos versions sont reproductibles et fonctionnent sous n'importe quel système d'exploitation sur lequel Nix peut être installé.


Nous avons donc commencé par créer une superposition Nix avec une expression pour construire TON . Son utilisation pour compiler TON est aussi simple que possible:


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

Notez que vous n'avez pas besoin d'installer de dépendances. Par magie, Nix fera tout pour vous, que vous utilisiez NixOS, Ubuntu ou macOS.


Programmation pour TON


Le code de contrat intelligent TON Network est exécuté sur la machine virtuelle TON (TVM). TVM est plus compliqué que la plupart des autres machines virtuelles et possède une fonctionnalité très intéressante, par exemple, il peut fonctionner avec des continuations et des liens vers des données .


De plus, les gars de TON ont créé trois nouveaux langages de programmation:


Fift est un langage de programmation de pile universel qui rappelle Forth . Sa super capacité est la capacité d'interagir avec TVM.


FunC est un langage de programmation de contrat intelligent qui est similaire à C et compilé dans un autre langage - Fift Assembler.


Fift Assembler - Bibliothèque Fift pour générer du code exécutable binaire pour TVM. Fift Assembler n'a pas de compilateur. Il s'agit d'un langage embarqué spécifique au domaine (eDSL) .


Nos travaux compétitifs


Enfin, il est temps de regarder les résultats de nos efforts.


Canal de paiement asynchrone


Canal de paiement - un contrat intelligent qui permet à deux utilisateurs d'envoyer des paiements en dehors de la blockchain. En conséquence, non seulement de l'argent est économisé (il n'y a pas de commission), mais aussi du temps (vous n'avez pas à attendre que le prochain bloc soit traité). Les paiements peuvent être arbitrairement petits et se produire aussi souvent que nécessaire. Dans le même temps, les parties n'ont pas besoin de se faire confiance, car l'équité du règlement final est garantie par un contrat intelligent.


Nous avons trouvé une solution assez simple au problème. Deux parties peuvent échanger des messages signés, chacun contenant deux numéros - le montant total payé par chacun des participants. Ces deux nombres fonctionnent comme des horloges vectorielles dans les systèmes distribués traditionnels et définissent l'ordre «arrivé avant» sur les transactions. En utilisant ces données, le contrat sera en mesure de résoudre tout conflit possible.


En fait, pour mettre en œuvre cette idée, un nombre suffit, mais nous avons laissé les deux, car nous avons pu créer une interface utilisateur plus pratique. De plus, nous avons décidé d'inclure un montant de paiement dans chaque message. Sans cela, si le message est perdu pour une raison quelconque, bien que tous les montants et le calcul final soient corrects, l'utilisateur peut ne pas remarquer la perte.


Pour tester notre idée, nous avons cherché des exemples d'utilisation d'un protocole de canal de paiement aussi simple et concis. Étonnamment, nous n'en avons trouvé que deux:


  1. Description d'une approche similaire, uniquement pour le cas d'un canal unidirectionnel.
  2. Un tutoriel qui décrit la même idée que la nôtre, mais sans expliquer de nombreux détails importants, tels que l'exactitude générale et la procédure de résolution des conflits.

Il est devenu clair qu'il est logique de décrire notre protocole en détail, en accordant une attention particulière à son exactitude. Après plusieurs itérations, la spécification était prête, et maintenant vous aussi pouvez la regarder.


Nous avons implémenté un contrat pour FunC, et nous avons écrit l'utilitaire de ligne de commande pour interagir avec notre contrat dans Fift, comme recommandé par les organisateurs. Nous pouvions choisir n'importe quelle autre langue pour notre CLI, mais c'était intéressant pour nous d'essayer Fift pour voir comment elle se montrait en action.


Honnêtement, après avoir travaillé avec Fift, nous n'avons vu aucune bonne raison de préférer cette langue aux langues populaires et activement utilisées avec des outils et des bibliothèques développés. La programmation dans le langage de pile est plutôt désagréable, car vous devez constamment garder à l'esprit ce qui se trouve sur la pile, et le compilateur n'aide pas.


Par conséquent, la seule justification, à notre avis, de l'existence de Fift est son rôle en tant que langue hôte du Fift Assembler. Mais ne serait-il pas préférable d'incorporer l'assembleur TVM dans un langage existant, et de ne pas en proposer un nouveau pour cela, essentiellement le seul but?


TVM Haskell eDSL


Il est maintenant temps de parler de notre deuxième contrat intelligent. Nous avons décidé de développer un portefeuille multi-signatures, mais écrire un autre contrat intelligent sur FunC serait trop ennuyeux. Nous voulions ajouter un peu de zeste, et c'est devenu notre propre langage d'assemblage pour TVM.


Comme Fift Assembler, notre nouveau langage est intégrable, mais au lieu de Fift, nous avons choisi Haskell comme hôte, ce qui nous a permis d'utiliser pleinement son système de type avancé. Lorsque vous travaillez avec des contrats intelligents, où le prix d'une petite erreur peut être très élevé, le typage statique, à notre avis, est un gros avantage.


Pour montrer à quoi ressemble l'assembleur TVM intégré à Haskell, nous avons implémenté un portefeuille standard dessus. Voici quelques points à surveiller:


  • Ce contrat comprend une fonction, mais vous pouvez en utiliser autant que vous le souhaitez. Lorsque vous définissez une nouvelle fonction dans le langage hôte (c'est-à-dire dans Haskell), notre eDSL vous permet de choisir si vous souhaitez qu'elle devienne un sous-programme distinct dans TVM ou soit simplement intégrée au lieu de l'appel.
  • Comme Haskell, les fonctions ont des types qui sont vérifiés au moment de la compilation. Dans notre eDSL, le type d'entrée de fonction est le type de pile que la fonction attend et le type de résultat est le type de pile qui sera obtenu après l'appel.
  • Le code a des annotations de type de pile qui décrivent le type de pile attendu au niveau du pair de numérotation. Dans le contrat de portefeuille d'origine, il ne s'agissait que de commentaires, mais dans notre eDSL, ils font en fait partie du code et sont vérifiés au moment de la compilation. Ils peuvent servir de documentation ou d'instructions qui aident le développeur à trouver le problème si le type de pile change à mesure que le code change. Bien entendu, ces annotations n'affectent pas les performances d'exécution, car aucun code TVM n'est généré pour elles.
  • Il s'agit toujours d'un prototype écrit en deux semaines, il reste donc beaucoup de travail à faire sur le projet. Par exemple, toutes les instances de classe que vous voyez dans le code ci-dessous doivent être générées automatiquement.

Voici à quoi ressemble l'implémentation du portefeuille multisig sur notre 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 

Le code source complet de notre eDSL et le contrat de portefeuille multi-signatures se trouvent dans ce référentiel. Et plus en détail, notre collègue George Agapov a parlé des langues intégrées.


Conclusions du concours et TON


Au total, notre travail a duré 380 heures (avec la connaissance de la documentation, des réunions et du développement lui-même). Cinq développeurs ont participé au concours: STO, chef d'équipe, spécialistes de la plateforme blockchain et développeurs de logiciels Haskell.


Nous avons trouvé les ressources pour participer au concours sans difficulté, car l'esprit du hackathon, le travail d'équipe rapproché, la nécessité d'une immersion rapide dans les aspects des nouvelles technologies sont toujours passionnants. Quelques nuits blanches afin d'obtenir des résultats maximaux dans des conditions de ressources limitées sont compensées par une expérience inestimable et d'excellents souvenirs. De plus, le travail sur de telles tâches est toujours un bon test des processus de l'entreprise, car il est extrêmement difficile d'obtenir des résultats vraiment décents sans une interaction interne parfaitement réglée.


Mis à part les paroles: nous avons été impressionnés par la quantité de travail effectuée par l'équipe TON. Ils ont réussi à construire un système complexe, beau et, surtout, fonctionnant. TON s'est avéré être une plateforme à fort potentiel. Cependant, pour que cet écosystème se développe, il reste encore beaucoup à faire, tant en termes d'utilisation dans les projets de blockchain qu'en termes d'amélioration des outils de développement. Nous sommes fiers de faire partie de ce processus maintenant.


Si après avoir lu cet article, vous avez encore des questions ou des idées sur la façon d'appliquer TON pour résoudre vos problèmes, écrivez-nous - nous serons heureux de partager notre expérience.

Source: https://habr.com/ru/post/fr476756/


All Articles