Transformer FunC en FunCtional avec Haskell : comment Serokell a remportĂ© le concours Telegram Blockchain

Vous avez probablement entendu ce tĂ©lĂ©gramme est sur le point de lancer la plateforme blockchain Ton. Mais vous avez peut-ĂȘtre ratĂ© 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, possédant une vaste expérience dans le développement de grands projets 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 (peu) modeste et aléatoire de Sexy Chameleon. Dans cet article, je vais parler de la façon dont ils l'ont fait. Nous espérons qu'au cours des dix prochaines minutes, vous lirez au moins une histoire intéressante et que vous y trouverez tout au plus quelque chose d'utile que vous pourrez appliquer dans votre travail.

Mais commençons par un peu de contexte.

La concurrence et ses conditions

Ainsi, les tĂąches principales des participants Ă©taient la mise en Ɠuvre d'un ou plusieurs des contrats intelligents proposĂ©s, ainsi que la formulation de propositions visant Ă  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. Assez longtemps, sachant que pendant ce temps Telegram a rĂ©ussi Ă  organiser 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 contrats intelligents dans la liste proposée par les organisateurs. Pour l'un d'eux, nous avons utilisé des outils distribués avec TON, et le second a été 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 pensons que la complexité des langages fonctionnels est une énorme exagération et pourquoi nous les préférons généralement aux langages orientés objet. D'ailleurs, il contient également original de cet article.

Pourquoi avons-nous décidé de participer ?

En bref, parce que notre spécialisation concerne les projets non standards et complexes qui nécessitent des compétences particuliÚres et ont souvent une valeur scientifique pour la communauté informatique. Nous soutenons fermement le développement du code source ouvert, nous nous engageons dans sa vulgarisation et coopérons également avec les principales universités russes dans le domaine de l'informatique et des mathématiques.

Les tĂąches intĂ©ressantes du concours et l'implication dans notre projet Telegram bien-aimĂ© Ă©taient en soi une excellente motivation, mais le fonds du prix est devenu une incitation supplĂ©mentaire. 🙂

Recherche sur la blockchain TON

Nous suivons de prĂšs les nouveaux dĂ©veloppements dans les domaines de la blockchain, de l’intelligence artificielle et de l’apprentissage automatique et essayons de ne manquer aucune version significative dans chacun des domaines dans lesquels nous travaillons. Par consĂ©quent, au moment oĂč le concours a commencĂ©, notre Ă©quipe connaissait dĂ©jĂ  les idĂ©es de Livre blanc TON. Cependant, avant de commencer Ă  travailler avec TON, nous n'avons pas analysĂ© la documentation technique et le code source rĂ©el de la plateforme, la premiĂšre Ă©tape Ă©tait donc assez Ă©vidente : une Ă©tude approfondie de la documentation officielle sur En ligne et rĂ©fĂ©rentiels de 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 utilisateurs. Malheureusement, cela n'a donné aucun résultat - à part les instructions pour assembler la plateforme sur Ubuntu, nous n'avons trouvé aucun autre matériel.

La documentation elle-mĂȘme Ă©tait bien documentĂ©e, mais Ă©tait difficile Ă  lire dans certains domaines. TrĂšs souvent, nous avons dĂ» revenir sur certains points et passer de descriptions de haut niveau d'idĂ©es abstraites Ă  des dĂ©tails de mise en Ɠuvre de bas niveau.

Ce serait plus facile si la spĂ©cification n'incluait pas du tout de description dĂ©taillĂ©e de la mise en Ɠuvre. Les informations sur la façon dont une machine virtuelle reprĂ©sente sa pile sont plus susceptibles de distraire les dĂ©veloppeurs crĂ©ant des contrats intelligents pour la plate-forme TON que de les aider.

Nix : monter le projet

Chez Serokell, nous sommes de grands fans Nix. Nous collectons nos projets avec et les dĂ©ployons en utilisant NixOps, et installĂ© sur tous nos serveurs Nix OS. GrĂące Ă  cela, toutes nos builds 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 Superposition Nix avec expression pour l'assemblage TON. Avec son aide, 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. Nix fera tout pour vous comme par magie, que vous utilisiez NixOS, Ubuntu ou macOS.

Programmation pour TON

Le code de contrat intelligent du réseau TON s'exécute sur la machine virtuelle TON (TVM). TVM est plus complexe que la plupart des autres machines virtuelles et possÚde des fonctionnalités trÚs intéressantes, par exemple, il peut fonctionner avec suites О liens vers des données.

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

Cinquante est un langage de programmation à pile universelle qui ressemble à En avant. Sa super capacité est la capacité d'interagir avec TVM.

FunC est un langage de programmation de contrats intelligents similaire à C et est compilé dans un autre langage - Fift Assembler.

CinquiĂšme Assembleur — CinquiĂšme bibliothĂšque pour gĂ©nĂ©rer du code exĂ©cutable binaire pour TVM. Fifth Assembler n'a pas de compilateur. Ce Langage spĂ©cifique au domaine intĂ©grĂ© (eDSL).

Notre concours fonctionne

Enfin, il est temps d'examiner les résultats de nos efforts.

Canal de paiement asynchrone

Le canal de paiement est un contrat intelligent qui permet Ă  deux utilisateurs d'envoyer des paiements en dehors de la blockchain. En consĂ©quence, vous Ă©conomisez non seulement de l’argent (il n’y a pas de commission), mais aussi du temps (vous n’avez pas besoin d’attendre que le bloc suivant soit traitĂ©). Les paiements peuvent ĂȘtre aussi modestes que souhaitĂ©s et aussi souvent que nĂ©cessaire. Dans ce cas, les parties ne sont pas obligĂ©es de se faire confiance, puisque l’équitĂ© du rĂšglement final est garantie par le contrat intelligent.

Nous avons trouvé une solution assez simple au problÚme. Deux parties peuvent échanger des messages signés, chacun contenant deux chiffres, soit le montant total payé par chaque partie. Ces deux nombres fonctionnent comme horloge de vecteur dans les systÚmes distribués traditionnels et définir l'ordre « arrivé avant » sur les transactions. Grùce à ces données, le contrat pourra résoudre tout conflit éventuel.

En fait, un seul numĂ©ro suffit pour mettre en Ɠuvre cette idĂ©e, mais nous avons laissĂ© les deux car nous pourrions ainsi crĂ©er une interface utilisateur plus pratique. De plus, nous avons dĂ©cidĂ© d'inclure le montant du paiement dans chaque message. Sans cela, si le message est perdu pour une raison quelconque, mĂȘme si tous les montants et le calcul final seront corrects, l'utilisateur risque de ne pas remarquer la perte.

Pour tester notre idĂ©e, nous avons recherchĂ© 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 une approche similaire, uniquement pour le cas d'un canal unidirectionnel.
  2. Didacticiel, 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 les procĂ©dures de rĂ©solution des conflits.

Il est devenu Ă©vident qu'il Ă©tait 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 le pouvez aussi. regarde la.

Nous avons implémenté le contrat dans FunC et nous avons écrit l'utilitaire de ligne de commande pour interagir avec notre contrat entiÚrement dans Fift, comme recommandé par les organisateurs. Nous aurions pu choisir n'importe quel autre langage pour notre CLI, mais nous souhaitions essayer Fit pour voir comment il fonctionnait dans la pratique.

Pour ĂȘtre honnĂȘte, aprĂšs avoir travaillĂ© avec Fift, nous n'avons vu aucune raison impĂ©rieuse de prĂ©fĂ©rer ce langage aux langages populaires et activement utilisĂ©s avec des outils et des bibliothĂšques dĂ©veloppĂ©s. Programmer dans un langage basĂ© sur la pile est assez dĂ©sagrĂ©able, car vous devez constamment garder en tĂȘte ce qui se trouve sur la pile, et le compilateur ne vous aide pas.

Par consĂ©quent, Ă  notre avis, la seule justification de l’existence de Fift est son rĂŽle de langage hĂŽte pour Fift Assembler. Mais ne serait-il pas prĂ©fĂ©rable d'intĂ©grer l'assembleur TVM dans un langage existant, plutĂŽt que d'en inventer un nouveau dans ce 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 dans FunC serait trop ennuyeux. Nous voulions ajouter un peu de saveur, et c'était notre propre langage d'assemblage pour TVM.

Comme Fift Assembler, notre nouveau langage est intĂ©grĂ©, mais nous avons choisi Haskell comme hĂŽte au lieu de Fift, ce qui nous permet de profiter pleinement de son systĂšme de types avancĂ©. Lorsque l’on travaille avec des contrats intelligents, oĂč le coĂ»t mĂȘme d’une petite erreur peut ĂȘtre trĂšs Ă©levĂ©, la saisie statique constitue, Ă  notre avis, un gros avantage.

Pour dĂ©montrer Ă  quoi ressemble l'assembleur TVM intĂ©grĂ© dans Haskell, nous avons implĂ©mentĂ© un portefeuille standard dessus. Voici quelques Ă©lĂ©ments auxquels il faut prĂȘter attention :

  • 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 Haskell), notre eDSL vous permet de choisir si vous souhaitez qu'elle devienne une routine distincte dans TVM ou simplement intĂ©grĂ©e au point d'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 d'une fonction est le type de pile attendu par la fonction, et le type de rĂ©sultat est le type de pile qui sera produit aprĂšs l'appel.
  • Le code a des annotations stacktype, dĂ©crivant le type de pile attendu au point d'appel. Dans le contrat original du portefeuille, 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 code change et le type de pile change. Bien entendu, de telles annotations n’ont pas d’impact sur les performances d’exĂ©cution, puisqu’aucun code TVM n’est gĂ©nĂ©rĂ© pour elles.
  • Il s'agit encore d'un prototype Ă©crit en deux semaines, il reste donc encore beaucoup de travail Ă  faire sur le projet. Par exemple, toutes les instances des classes que vous voyez dans le code ci-dessous doivent ĂȘtre gĂ©nĂ©rĂ©es automatiquement.

Voici Ă  quoi ressemble la mise en place d'un 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 contrat eDSL et portefeuille multi-signatures est disponible sur ce référentiel. Et plus raconté en détail sur les langages intégrés, notre collÚgue Georgy Agapov.

Conclusions sur la compétition et TON

Au total, notre travail a duré 380 heures (y compris la familiarisation avec la documentation, les réunions et le développement proprement dit). Cinq développeurs ont participé au projet du concours : le CTO, le chef d'équipe, les spécialistes de la plateforme blockchain et les développeurs de logiciels Haskell.

Nous avons trouvĂ© les ressources pour participer au concours sans difficultĂ©, car l'esprit d'un hackathon, le travail d'Ă©quipe serrĂ© et le besoin de s'immerger rapidement dans les aspects des nouvelles technologies sont toujours passionnants. Plusieurs nuits blanches pour obtenir des rĂ©sultats optimaux dans des conditions de ressources limitĂ©es sont compensĂ©es par une expĂ©rience inestimable et d'excellents souvenirs. De plus, travailler 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 qui fonctionne bien.

Paroles mises Ă  part : nous avons Ă©tĂ© impressionnĂ©s par la quantitĂ© de travail fourni par l’équipe de TON. Ils ont rĂ©ussi Ă  construire un systĂšme complexe, beau et, surtout, fonctionnel. TON s’est rĂ©vĂ©lĂ© ĂȘ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 blockchain qu’en termes 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 encore des questions ou des idĂ©es sur la façon d'utiliser TON pour rĂ©soudre vos problĂšmes, Ă©crivez-nous — nous serons heureux de partager notre expĂ©rience.

Source: habr.com

Ajouter un commentaire