Transformant FunC en FunCtional avec Haskell : comment Serokell a remporté le Telegram Blockchain Competition

Vous avez sĂ»rement entendu dire que Telegram prĂ©pare le lancement d'une plateforme blockchain Ton. Mais vous avez peut-ĂȘtre manquĂ© la nouvelle indiquant que Telegram a annoncĂ© un concours pour la mise en Ɠuvre d'un ou plusieurs contrats intelligents pour cette plateforme.

L'équipe Serokell, fort d'une riche expérience dans le développement de grands projets blockchain, ne pouvait pas rester en dehors. Nous avons délégué cinq employés au concours, et seulement deux semaines plus tard, ils ont remporté la premiÚre place sous le pseudo (non)modeste de Sexy Chameleon. Dans cet article, je vais vous raconter comment ils y sont parvenus. Nous espérons qu'au cours des dix prochaines minutes, vous lirez au minimum une histoire intéressante, et au maximum que vous trouviez quelque chose d'utile que vous pourrez appliquer dans votre travail.

Mais commençons par une petite immersion dans le contexte.

Concours 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 soumission 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. C'est assez long, considĂ©rant que pendant ce temps Telegram a rĂ©ussi Ă  mener et Ă  annoncer les rĂ©sultats des concours de design et de dĂ©veloppement d'applications en C++ pour tester et Ă©valuer la qualitĂ© des appels VoIP sur Telegram.

Nous avons choisi parmi la liste proposée par les organisateurs deux contrats intelligents. Pour l'un d'eux, nous avons utilisé les outils fournis avec TON, tandis que le second a été réalisé dans un nouveau langage développé par nos ingénieurs spécifiquement pour TON et intégré dans Haskell.

Le choix d'un langage de programmation fonctionnel n'est pas un hasard. Dans notre blog d'entreprise , nous expliquons souvent pourquoi nous pensons que la complexité des langages fonctionnels est une exagération et pourquoi nous préférez de maniÚre générale ces derniers aux langages orientés objet. Au fait, il contient également l' original de cet article.

Pourquoi avons-nous décidé de participer ?

En résumé, parce que notre spécialité réside dans des projets non standards et complexes, nécessitant des compétences particuliÚres et représentant souvent une valeur scientifique pour la communauté IT. Nous soutenons activement le développement open-source et nous nous engageons à sa promotion, tout en collaborant avec les principales universités de Russie dans le domaine des sciences informatiques et des mathématiques.

Les dĂ©fis intĂ©ressants du concours et l'implication dans le projet bien-aimĂ© Telegram Ă©taient dĂ©jĂ  une excellente motivation, et le fonds de prix a constituĂ© un stimulant supplĂ©mentaire. 🙂

Recherche sur la blockchain TON

Nous suivons de prÚs les nouvelles avancées dans le domaine de la blockchain, de l'intelligence artificielle et de l'apprentissage automatique, et nous veillons à ne manquer aucun lancement significatif dans chacun des domaines dans lesquels nous travaillons. Ainsi, au moment du lancement du concours, notre équipe était déjà familiÚre avec les idées de la white paper de TON. Cependant, avant de commencer à travailler avec TON, nous n'avions pas analysé la documentation technique ni le code source réel de la plateforme, donc la premiÚre étape était assez évidente : une étude approfondie de la documentation officielle sur 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 un résumé rédigé par des utilisateurs. Malheureusement, cela n'a pas donné de résultats : à part un manuel sur la compilation de la plateforme sur Ubuntu, nous n'avons trouvé aucun autre matériel.

La documentation elle-mĂȘme s'est rĂ©vĂ©lĂ©e soigneusement conçue, mais il Ă©tait parfois difficile de la lire. Il nous a souvent fallu revenir sur certains points et passer d'une description abstraite de haut niveau Ă  des dĂ©tails d'implĂ©mentation de bas niveau.

Il aurait été plus simple si la spécification n'avait pas du tout comporté de description détaillée de l'implémentation. Les informations sur la façon dont la machine virtuelle représente sa pile distraient davantage les développeurs qui créent des contrats intelligents pour la plateforme TON qu'elles ne les aident.

Nix : Construction du projet

Chez Serokell, nous sommes de grands fans Nix. Nous construisons nos projets et les dĂ©ployons Ă  l'aide de NixOps, et tous nos serveurs sont Ă©quipĂ©s de NixOS. GrĂące Ă  cela, toutes nos crĂ©ations sont reproductibles et fonctionnent sur n'importe quel systĂšme d'exploitation sur lequel Nix peut ĂȘtre installĂ©.

Nous avons donc commencé par créer un overlay Nix avec une expression pour la construction de TON. Avec celui-ci, compiler TON est un jeu d'enfant :

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

Notez qu'il n'est pas nécessaire d'installer des dépendances. Nix fera magiquement tout pour vous, que vous utilisiez NixOS, Ubuntu ou macOS.

Programmation pour TON

Le code des smart contracts dans le réseau TON est exécuté sur la TON Virtual Machine (TVM). La TVM est plus complexe que la plupart des autres machines virtuelles et possÚde des fonctionnalités trÚs intéressantes, par exemple elle peut travailler avec des continuations (continuations) et des liens vers des données.

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

Fift — un langage de programmation basĂ© sur la pile, similaire Ă  Forth. Sa super-pouvoir est la capacitĂ© d'interagir avec la TVM.

FunC — un langage de programmation de smart contracts, qui ressemble à C et se compile en un autre langage — le Fift Assembler.

Fift Assembler — une bibliothĂšque Fift pour la gĂ©nĂ©ration de code binaire exĂ©cutable pour la TVM. Le Fift Assembler n'a pas de compilateur. C'est un langage orientĂ© domaine embarquĂ© (eDSL).

Nos travaux de concours

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

Canal de paiement asynchrone

Un canal de paiement (payment channel) est un smart contract qui permet Ă  deux utilisateurs d'envoyer des paiements en dehors de la blockchain. Cela permet non seulement d'Ă©conomiser de l'argent (pas de frais), mais aussi du temps (vous n'avez pas Ă  attendre que le prochain bloc soit traitĂ©). Les paiements peuvent ĂȘtre aussi petits que nĂ©cessaire et se produire aussi souvent que requis. De plus, les parties n'ont pas besoin de se faire confiance, car l'Ă©quitĂ© du rĂšglement final est garantie par le smart contract.

Nous avons trouvĂ© une solution assez simple au problĂšme. Les deux parties peuvent Ă©changer des messages signĂ©s, chacun contenant deux nombres — le montant total payĂ© par chaque participant. Ces deux nombres fonctionnent comme les horloges vectorielles dans les systĂšmes distribuĂ©s traditionnels et Ă©tablissent l'ordre de « s'est produit avant » sur les transactions. En utilisant ces donnĂ©es, le contract pourra rĂ©soudre tout conflit potentiel.

En réalité, il suffirait d'un seul nombre pour réaliser cette idée, mais nous avons conservé les deux, car cela nous permettait de créer une interface utilisateur plus pratique. De plus, nous avons décidé d'inclure dans chaque message la taille du paiement. Sans cela, si un message est perdu pour une raison quelconque, bien que tous les montants et le rÚglement final soient corrects, l'utilisateur risque de ne pas se rendre compte de la perte.

Pour vĂ©rifier notre idĂ©e, nous avons recherchĂ© des exemples d'utilisation d'un protocole de canal de paiement aussi simple et concis. À notre grande surprise, nous n'en avons trouvĂ© que deux:

  1. Description une approche similaire, mais pour un canal unidirectionnel.
  2. Tutoriel, qui dĂ©crit la mĂȘme idĂ©e que la nĂŽtre, mais sans expliquer de nombreux dĂ©tails importants, tels que la correction globale et la procĂ©dure de rĂ©solution des conflits.

Il est devenu Ă©vident qu'il Ă©tait nĂ©cessaire de dĂ©crire notre protocole en dĂ©tail, en mettant particuliĂšrement l'accent sur sa correction. AprĂšs plusieurs itĂ©rations, la spĂ©cification Ă©tait prĂȘte, et maintenant vous pouvez aussi y jeter un Ɠil.

Nous avons implémenté le contrat en FunC, et l'utilitaire en ligne de commande pour interagir avec notre contrat a été entiÚrement écrit en Fift, comme recommandé par les organisateurs. Nous aurions pu choisir n'importe quel autre langage pour notre CLI, mais nous étions intéressés à essayer Fift pour voir comment il se comporterait en pratique.

HonnĂȘtement, aprĂšs avoir travaillĂ© avec Fift, nous n'avons pas vu de raisons convaincantes de prĂ©fĂ©rer ce langage Ă  des langages populaires et largement utilisĂ©s avec des outils et des bibliothĂšques dĂ©veloppĂ©s. Programmer dans un langage de pile est assez dĂ©sagrĂ©able, car il faut constamment garder Ă  l'esprit ce qui se trouve dans la pile, et le compilateur n'aide pas vraiment.

Ainsi, la seule justification de l'existence de Fift, à notre avis, est son rÎle en tant que langage hÎte pour l'assembleur Fift. Mais ne serait-il pas mieux d'intégrer l'assembleur TVM dans un langage existant plutÎt que d'en inventer un nouveau uniquement pour cette fin?

TVM Haskell eDSL

Il est maintenant temps de parler de notre deuxiÚme contrat intelligent. Nous avons décidé de développer un portefeuille avec multisignature, mais écrire un autre contrat intelligent en FunC serait trop ennuyeux. Nous voulions ajouter une touche originale, et celle-ci est devenue notre propre langage d'assemblage pour TVM.

Tout comme l'assembleur Fift, notre nouveau langage est embarquĂ©, mais au lieu de Fift, nous avons choisi Haskell comme hĂŽte, ce qui nous a permis de tirer pleinement parti de son systĂšme de types avancĂ©. Travailler avec des contrats intelligents, oĂč le coĂ»t d'une petite erreur peut ĂȘtre trĂšs Ă©levĂ©, nous semble ĂȘtre un avantage considĂ©rable de la typage statique.

Pour démontrer à quoi ressemble l'assembleur TVM intégré dans Haskell, nous l'avons utilisé pour implémenter un portefeuille standard. Voici quelques éléments à considérer :

  • Ce contrat se compose d'une seule fonction, mais vous pouvez en utiliser autant que nĂ©cessaire. Lorsque vous dĂ©finissez une nouvelle fonction dans le langage hĂŽte (c'est-Ă -dire en Haskell), notre eDSL vous permet de choisir si vous souhaitez qu'elle devienne un sous-programme distinct dans TVM ou simplement intĂ©grĂ©e au point d'appel.
  • Comme en Haskell, les fonctions ont des types qui sont vĂ©rifiĂ©s lors de la compilation. Dans notre eDSL, le type d'entrĂ©e de la fonction est le type de pile attendu, tandis que le type de rĂ©sultat est le type de pile qui sera obtenu aprĂšs l'appel.
  • Le code contient des annotations stacktype, dĂ©crivant le type de pile attendu au point d'appel. Dans le contrat original du portefeuille, il s'agissait simplement de commentaires, mais dans notre eDSL, elles font rĂ©ellement partie du code et sont vĂ©rifiĂ©es lors de la compilation. Elles peuvent servir de documentation ou d'assertions qui aident le dĂ©veloppeur Ă  identifier un problĂšme si le type de pile change lors de modifications du code. Naturellement, ces annotations n'affectent pas les performances Ă  l'exĂ©cution, car aucun code TVM n'est gĂ©nĂ©rĂ© pour elles.
  • C'est toujours un prototype, Ă©crit en deux semaines, donc il reste encore beaucoup de travail Ă  faire sur le projet. Par exemple, toutes les instances de classes que vous voyez dans le code ci-dessous doivent ĂȘtre gĂ©nĂ©rĂ©es automatiquement.

Voici à quoi ressemble l'implémentation d'un portefeuille multisig dans notre eDSL :

principal :: IO ()
principal = putText $ pretty $ declProgram procedures methods
  oĂč
    procédures =
      [ ("recv_external", decl recvExternal)
      , ("recv_internal", decl recvInternal)
      ]
    méthodes =
      [ ("seqno", declMethod getSeqno)
      ]

donnée Stockage = Stockage
  { sCnt :: Word32
  , sPubKey :: CléPublique
  }

instance DecodeSlice Stockage oĂč
  type DecodeSliceFields Stockage = [CléPublique, Word32]
  decodeFromSliceImpl = do
    decodeFromSliceImpl @Word32
    decodeFromSliceImpl @CléPublique

instance EncodeBuilder Stockage oĂč
  encodeToBuilder = do
    encodeToBuilder @Word32
    encodeToBuilder @CléPublique

donnée ErreurDePortefeuille
  = SeqNoMismatch
  | SignatureMismatch
  dérivé (Eq, Ord, Show, Générique)

instance Exception ErreurDePortefeuille

instance Enum ErreurDePortefeuille oĂč
  toEnum 33 = SeqNoMismatch
  toEnum 34 = SignatureMismatch
  toEnum _ = error "Id MultiSigError inconnu"

  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 @Stockage
  stacktype @[CléPublique, Word32, Word32, Slice, Signature]
  -- pk cnt' cnt cs sign

  xcpu @1 @2
  stacktype @[Word32, Word32, CléPublique, Word32, Slice, Signature]
  -- cnt cnt' pk cnt cs sign

  equalInt >> throwIfNot SeqNoMismatch

  push @2
  sliceHash
  stacktype @[Hash Slice, CléPublique, Word32, Slice, Signature]
  -- hash pk cnt cs sign

  xc2pu @0 @4 @4
  stacktype @[CléPublique, Signature, Hash Slice, Word32, Slice, CléPublique]
  -- pubk sign hash cnt cs pubk

  chkSignU
  stacktype @[Bool, Word32, Slice, CléPublique]
  -- ? 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, CléPublique]
    xchg @2
    sendRawMsg
    stacktype @[Slice, Word32, CléPublique]

  endS
  inc

  encodeToCell @Stockage
  popRoot

getSeqno :: '[] :-> '[Word32]
getSeqno = do
  pushRoot
  cToS
  preloadFromSlice @Word32

Vous pouvez trouver le code source complet de notre eDSL et du contrat de portefeuille avec multip signature dans ce dépÎt. Notre collÚgue Georgy Agapov a expliqué plus en détail sur les langages intégrés. Les conclusions sur le concours et TON

En tout, notre travail a pris 380 heures (y compris la prise de connaissance de la documentation, les réunions et le développement proprement dit). Cinq développeurs ont participé au projet du concours : CTO, team lead, spécialistes des plateformes blockchain et développeurs de logiciels Haskell.

À propos du projet à gagner.

Les ressources pour participer au concours ont été faciles à trouver, car l'esprit du hackathon, le travail d'équipe rapproché et la nécessité de plonger rapidement dans les aspects de nouvelles technologies sont toujours passionnants. Quelques nuits blanches pour obtenir le meilleur résultat dans des conditions de ressources limitées sont compensées par une expérience inestimable et d'excellents souvenirs. De plus, travailler sur ce type de tùches est toujours un bon test des processus de l'entreprise, car obtenir des résultats vraiment dignes sans une interaction interne parfaitement réglée est trÚs difficile.

Au-delĂ  de la lyrisme : nous avons Ă©tĂ© impressionnĂ©s par l’ampleur du travail rĂ©alisĂ© par l’équipe TON. Ils ont rĂ©ussi Ă  construire un systĂšme complexe, beau, et surtout, fonctionnel. TON a montrĂ© qu'il s'agit d'une plateforme ayant un grand potentiel. Cependant, pour que cet Ă©cosystĂšme se dĂ©veloppe, beaucoup de travail doit encore ĂȘtre fait, tant en ce qui concerne son utilisation dans des projets blockchain qu'en matiĂšre d'amĂ©lioration des outils de dĂ©veloppement. Nous sommes fiers de faire dĂ©sormais partie de ce processus.

Si aprĂšs avoir lu cet article, vous avez des questions ou des idĂ©es sur la façon d'appliquer TON Ă  vos besoins, contactez-nous — nous serons ravis de partager notre expĂ©rience.

Source : habr.com

Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS đŸ”„ Acheter un hĂ©bergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster