TON: recommandations et bonnes pratiques

Cet article est la traduction d'un document publiĂ© sur la page blockchain de TON: smc-guidelines.txt . Peut-ĂȘtre que cela aidera quelqu'un Ă  faire un pas vers le dĂ©veloppement de cette blockchain. De plus, Ă  la fin, j'ai fait un bref rĂ©sumĂ©.


Messages internes


Les contrats intelligents interagissent les uns avec les autres en envoyant des messages dits internes. Lorsque le message interne atteint sa destination spĂ©cifiĂ©e, une transaction rĂ©guliĂšre est crĂ©Ă©e au nom du compte de destination et le message interne est traitĂ© selon le code spĂ©cifiĂ© et les donnĂ©es constantes de ce compte (contrat intelligent). En particulier, une transaction de traitement peut crĂ©er un ou plusieurs messages internes, dont certains peuvent ĂȘtre adressĂ©s Ă  l'adresse source du message interne en cours de traitement. Cela peut ĂȘtre utilisĂ© pour crĂ©er de simples «applications client-serveur» lorsqu'une demande est intĂ©grĂ©e (encapsulĂ©e) dans un message interne et envoyĂ©e Ă  un autre contrat intelligent qui traite la demande et renvoie la rĂ©ponse, Ă  nouveau sous forme de message interne.


Cette approche conduit Ă  la nĂ©cessitĂ© de distinguer les messages internes Ă  "demander" et "rĂ©ponse" (en tant que "requĂȘte" ou en tant que "rĂ©ponse"), ou qui ne nĂ©cessitent aucun traitement supplĂ©mentaire (comme un simple transfert d'argent). De plus, lorsqu'une rĂ©ponse arrive, il doit y avoir un moyen de comprendre Ă  quelle demande elle se rapporte.


Pour atteindre cet objectif, il est recommandé d'utiliser le modÚle de message interne suivant (rappelez-vous que la blockchain TON n'impose aucune restriction au corps du message, c'est-à-dire qu'il s'agit simplement d'une recommandation):


0) Le corps du message peut ĂȘtre intĂ©grĂ© dans le message lui-mĂȘme, ou il peut ĂȘtre stockĂ© dans une cellule distincte (cellule *), qui est rĂ©fĂ©rencĂ©e dans le message, comme indiquĂ© dans le fragment TL-B du diagramme (en anglais, il est plus facile Ă  comprendre: ou ĂȘtre stockĂ© dans un autre cellule rĂ©fĂ©rencĂ©e dans le message, comme indiquĂ© par le fragment de schĂ©ma TL-B):


message$_ {X:Type} ... body:(Either X ^X) = Message X; 

( https://core.telegram.org/mtproto - ici vous pouvez lire sur les schémas TL)


Le contrat intelligent de rĂ©ception doit accepter au moins les messages internes avec le corps du message intĂ©grĂ© (mĂȘme s'ils sont placĂ©s dans la cellule contenant le message - chaque fois qu'ils s'insĂšrent dans la cellule contenant le message - il n'est pas trĂšs clair ce que cela signifie, par consĂ©quent, joint le texte d'origine). Si le contrat accepte les corps de message dans des cellules distinctes (en utilisant le constructeur "droit" (Either X ^X) ), le traitement du message entrant ne devrait pas dĂ©pendre de la mĂ©thode particuliĂšre d'intĂ©gration du corps du message. D'un autre cĂŽtĂ©, il est absolument lĂ©gal de ne pas prendre en charge le corps du message dans une cellule distincte pour simplifier les demandes et les rĂ©ponses.


1) Le corps du message commence généralement par les champs suivants:


  • op - Entier non signĂ© 32 bits (big-endian) qui identifie l'opĂ©ration Ă  exĂ©cuter ou la mĂ©thode de contrat intelligent Ă  appeler.
  • query_id est un entier non signĂ© 64 bits (big-endian) utilisĂ© dans tous les messages de questions et rĂ©ponses internes pour identifier la relation de la rĂ©ponse Ă  la demande (le query_id de la rĂ©ponse doit ĂȘtre Ă©gal Ă  la query_id de la demande correspondante). Si op n'est pas une mĂ©thode de demande-rĂ©ponse (il appelle une mĂ©thode Ă  partir de laquelle aucune rĂ©ponse n'est attendue), alors query_id peut ĂȘtre omis.
  • le reste du corps du message est spĂ©cifique Ă  chaque valeur prise en charge du paramĂštre op

2) Si op est nul, le message est un simple message de transfert avec un commentaire. Le commentaire est contenu dans le reste du message (sans query_id et ainsi de suite, c'est-Ă -dire Ă  partir du 5Ăšme octet (explication: si query_id ne l' est pas, alors le champ op occupe les 4 premiers octets)). S'il ne commence pas par l'octet 0xff, le commentaire est un texte;); il peut ĂȘtre affichĂ© pour l'utilisateur final du portefeuille "tel quel" (aprĂšs avoir filtrĂ© les caractĂšres invalides et de contrĂŽle et vĂ©rifiĂ© qu'il s'agit d'une chaĂźne UTF-8 valide). Par exemple, les utilisateurs peuvent spĂ©cifier le but d'un simple transfert de leur portefeuille vers le portefeuille d'un autre utilisateur dans ce champ. En revanche, si un commentaire commence par l'octet 0xff, le reste du message est un «commentaire binaire» qui ne doit pas ĂȘtre affichĂ© Ă  l'utilisateur final sous forme de texte (uniquement sous forme de vidage hexadĂ©cimal si nĂ©cessaire). L'utilisation proposĂ©e des commentaires binaires, par exemple, est de contenir un identifiant de paiement pour le paiement dans le magasin, et d'ĂȘtre automatiquement gĂ©nĂ©rĂ© et traitĂ© par le logiciel du magasin.


La plupart des contrats intelligents n'ont pas Ă  effectuer d'actions non triviales ou Ă  rejeter un message entrant lorsqu'ils reçoivent un «message de transfert simple». Ainsi, lorsque op s'avĂšre ĂȘtre nul, la fonction de contrat intelligent pour le traitement des messages internes entrants (gĂ©nĂ©ralement appelĂ©e recv_internal() ) doit immĂ©diatement se recv_internal() avec le code 0, indiquant le succĂšs (par exemple, lever l'exception 0 si un gestionnaire personnalisĂ© n'est pas installĂ© dans le contrat intelligent exceptions). Cela entraĂźnera le fait que le montant transfĂ©rĂ© par le message sera crĂ©ditĂ© sur le compte du destinataire sans autre effet.


3) "Un simple transfert de message sans commentaires" a un corps vide (mĂȘme sans le champ op ). Les considĂ©rations ci-dessus s'appliquent Ă  ces messages. Veuillez noter que ces messages doivent avoir leur propre corps intĂ©grĂ© dans la cellule de message.


4) Nous nous attendons Ă  ce que le champ op des messages de demande ait le premier bit ("bit de poids fort", traduit comme le premier, cela peut ĂȘtre incorrect, mais comme expliquĂ© plus tard, il devient clair) est vide, c'est-Ă -dire que la valeur du champ doit ĂȘtre dans la plage 1 .. 2^31-1 , et pour les messages de rĂ©ponse, le premier bit (de poids fort) doit ĂȘtre Ă©gal Ă  1, c'est-Ă -dire la valeur de champ dans la plage 2^31 .. 2^32-1 . Si le message n'est ni une demande ni une rĂ©ponse (le corps ne contient pas le paramĂštre query_id ), il doit contenir le paramĂštre op dans la plage comme dans le message de demande: 1 .. 2^31 - 1 .


5) Il existe plusieurs messages de réponse «standard» pour lesquels op est 0xffffffff et 0xffffffffe. En général, les valeurs op de 0xfffffff0 à 0xffffffff sont réservées pour de telles réponses standard.


  • op = 0xffffffff signifie "l'opĂ©ration n'est pas prise en charge". Il est suivi d'un ID de requĂȘte 64 bits extrait de la requĂȘte d'origine et d'un op 32 bits de la requĂȘte d'origine. Tous les contrats intelligents, sauf les plus simples, doivent renvoyer cette erreur lorsqu'ils reçoivent une demande avec un op inconnu dans la plage 1 ... 2 ^ 31-1.
  • op = 0xfffffffe signifie "opĂ©ration non autorisĂ©e". Il est suivi par l' identifiant de requĂȘte 64 bits de la requĂȘte d'origine, puis par l'opĂ©ration 32 bits extraite de la requĂȘte d'origine.

Notez que les "rĂ©ponses" inconnues (avec op dans la plage 2 ^ 31 ... 2 ^ 32-1) doivent ĂȘtre ignorĂ©es (en particulier, vous ne devez pas gĂ©nĂ©rer de rĂ©ponse avec op Ă©gal Ă  0xffffffff), ainsi qu'un retour inattendu ( messages rebondis (avec le drapeau "rebondi").


Paiement pour le traitement des demandes et l'envoi des réponses


En général, si un contrat intelligent souhaite envoyer une demande à un autre contrat intelligent, il doit payer pour envoyer un message interne au contrat intelligent cible (frais de transfert de message), pour traiter ce message à destination (frais de gaz: frais de gaz) et pour l'envoi d'une réponse si nécessaire (frais d'envoi de messages).


Dans la plupart des cas, l'expéditeur attachera une petite quantité de gramme au message interne (par exemple, 1 gramme) (assez pour payer pour le traitement de ce message) et définira le drapeau "rebond" dessus (c'est-à-dire qu'il enverra un message interne rebondissable); le destinataire retournera la partie inutilisée de la valeur reçue avec la réponse (en soustrayant les frais d'envoi du message). Ceci est généralement réalisé en appelant SENDRAWMSG avec mode = 64 (cf. Annexe A de la documentation TON VM).


Si le destinataire ne peut pas traiter le message reçu et que l'exĂ©cution se termine avec un code de sortie diffĂ©rent de zĂ©ro (par exemple, en raison d'une exception de dĂ©sĂ©rialisation de cellule non gĂ©rĂ©e), le message sera automatiquement «renvoyé» Ă  l'expĂ©diteur et l'indicateur «rebond» sera dĂ©cochĂ© et dĂ©fini. drapeau "rebondi". Le corps du message renvoyĂ© sera le mĂȘme que le message d'origine; par consĂ©quent, il est important de vĂ©rifier l'indicateur "renvoyĂ©" du message interne entrant avant d'analyser le champ op dans le contrat intelligent et de traiter la demande correspondante (sinon il existe un risque que la demande contenue dans le message renvoyĂ© soit traitĂ©e par son expĂ©diteur d'origine comme une nouvelle demande distincte). Si l'indicateur "rebondi" est dĂ©fini, un code spĂ©cial peut comprendre quelle demande a Ă©chouĂ© (par exemple, en dĂ©sĂ©rialisant op et query_id Ă  partir d'un message renvoyĂ©) et prendre les mesures appropriĂ©es. Un contrat intelligent plus simple peut simplement ignorer tous les messages retournĂ©s (se terminer par un code de sortie nul si l'indicateur "rebondi" est dĂ©fini).


D'un autre cĂŽtĂ©, le rĂ©cepteur peut analyser correctement la demande entrante et constater que la mĂ©thode op demandĂ©e n'est pas prise en charge ou qu'une autre condition d'erreur a Ă©tĂ© remplie. Ensuite, une rĂ©ponse avec op Ă©gal Ă  0xffffffff ou une autre valeur appropriĂ©e doit ĂȘtre renvoyĂ©e en utilisant SENDRAWMSG avec mode = 64, comme mentionnĂ© ci-dessus.


Dans certaines situations, l'expĂ©diteur souhaite transfĂ©rer une certaine somme d'argent en mĂȘme temps? Ă  l'expĂ©diteur? (ici, apparemment, une erreur, et Ă©tait destinĂ© au "destinataire") et recevoir soit une confirmation soit un message d'erreur. Par exemple, un contrat intelligent d'Ă©lections de validateur reçoit une demande de participation Ă  une Ă©lection ainsi qu'une offre en tant que valeur ajoutĂ©e. Dans de tels cas, il est logique d'attacher, par exemple, un gramme supplĂ©mentaire Ă  la valeur estimĂ©e [coĂ»t] (Ici, le mot valeur est utilisĂ© partout, dans le sens de paiement pour une action, j'ai donc utilisĂ© le mot "coĂ»t"). Si une erreur se produit (par exemple, l'offre ne peut ĂȘtre acceptĂ©e pour une raison quelconque), le montant total reçu (moins les frais de traitement) doit ĂȘtre retournĂ© Ă  l'expĂ©diteur avec le message d'erreur (par exemple, en utilisant SENDRAWMSG avec le mode = 64, comme dĂ©crit ci-dessus). En cas de succĂšs, un message de confirmation est crĂ©Ă© et exactement un gramme est renvoyĂ© (les frais de transfert du message sont soustraits de cette valeur; il s'agit du mode = 1 de SENDRAWMSG).


Utilisation de messages non rebondissables


Presque tous les messages internes envoyĂ©s entre les contrats intelligents doivent ĂȘtre retournĂ©s (vous pouvez le traduire par "rebondir", mais afin de ne pas vous tromper, il est plus facile d'utiliser cette terminologie), c'est-Ă -dire qu'ils doivent avoir le bit "rebond" non vide. Ensuite, si le contrat intelligent cible n'existe pas ou s'il crĂ©e une exception non gĂ©rĂ©e lors du traitement de ce message, le message sera «renvoyé» en retour, supportant le reste du coĂ»t initial (valeur) (moins tous les frais de transmission des messages et du gaz). Le message renvoyĂ© aura le mĂȘme corps, mais avec le drapeau "bounce" effacĂ© et le drapeau "bounce" dĂ©fini. Par consĂ©quent, tous les contrats intelligents doivent vĂ©rifier le drapeau "rebondi" de tous les messages entrants et les recevoir en silence (se terminant immĂ©diatement avec un code de sortie nul) ou effectuer un traitement spĂ©cial pour dĂ©terminer quelle demande sortante a Ă©chouĂ©. La demande contenue dans le corps du message renvoyĂ© ne doit jamais ĂȘtre exĂ©cutĂ©e.


Dans certains cas, des messages internes non rebondissants doivent ĂȘtre utilisĂ©s. Par exemple, un nouveau compte ne peut pas ĂȘtre crĂ©Ă© sans qu'au moins un message interne irrĂ©vocable ne lui soit envoyĂ©. Si ce message ne contient pas StateInit avec le code et les donnĂ©es du nouveau contrat intelligent, cela n'a aucun sens d'avoir un corps non vide dans un message interne non retournĂ©.


C'est une bonne idée de ne pas permettre à l'utilisateur final (par exemple, le portefeuille) d'envoyer des messages irrévocables qui contiennent une grande quantité (par exemple, plus de cinq grammes), ou au moins de les avertir s'ils essaient de le faire. Il est préférable d'envoyer d'abord un petit montant, puis de créer un nouveau contrat intelligent, puis d'envoyer un montant plus important.


Messages externes


Les messages externes sont envoyés en externe aux contrats intelligents situés sur la blockchain TON pour les forcer à effectuer certaines actions. Par exemple, le contrat intelligent du portefeuille s'attend à recevoir des messages externes contenant des commandes (par exemple, des messages internes qui seront envoyés à partir du contrat intelligent du portefeuille) signés par le propriétaire du portefeuille; lorsqu'un tel message externe est reçu par le contrat intelligent du portefeuille, il vérifie d'abord la signature, puis reçoit le message (en lançant la primitive TVM ACCEPT), puis effectue toutes les actions nécessaires.


Veuillez noter que tous les messages externes doivent ĂȘtre protĂ©gĂ©s contre les attaques par rejeu. Les validateurs suppriment gĂ©nĂ©ralement un message externe du pool de messages externes proposĂ©s (reçus du rĂ©seau); cependant, dans certaines situations, un autre validateur peut traiter deux fois le mĂȘme message externe (crĂ©ant ainsi une deuxiĂšme transaction pour le mĂȘme message externe, ce qui conduit Ă  la duplication de l'action d'origine). Pire encore, un attaquant peut extraire un message externe d'un bloc contenant une transaction de traitement et le renvoyer ultĂ©rieurement. Cela peut entraĂźner, par exemple, un contrat de portefeuille intelligent pour rĂ©pĂ©ter le paiement.


Le moyen le plus simple de protĂ©ger les contrats intelligents contre les attaques de reniflement associĂ©es aux messages externes consiste Ă  stocker le compteur cur-seqno 32 bits dans les donnĂ©es constantes du contrat intelligent et Ă  attendre la valeur req-seqno dans la (partie signĂ©e) de tous les messages externes entrants. Le message externe n'est alors acceptĂ© (ACCEPTÉ - un indice de la primitive ACCEPT) que si la signature est valide et que req-seqno est Ă©gal Ă  cur-seqno . AprĂšs un traitement rĂ©ussi, la valeur de cur-seqno dans les donnĂ©es persistantes augmente de un, de sorte que le mĂȘme message externe ne sera plus jamais reçu.


Vous pouvez Ă©galement inclure le champ expirer Ă  dans un message externe et accepter le message uniquement si l'heure Unix actuelle est infĂ©rieure Ă  la valeur de ce champ. Cette approche peut ĂȘtre utilisĂ©e en combinaison avec seqno ; en variante, le contrat intelligent de rĂ©ception peut stocker un ensemble (hachages) de tous les derniers messages externes reçus (non expirĂ©s) dans ses donnĂ©es permanentes et rejeter un nouveau message externe s'il s'agit d'un doublon de l'un des messages enregistrĂ©s. Vous devez Ă©galement implĂ©menter la collecte et la suppression des messages expirĂ©s dans cet ensemble pour Ă©viter une croissance illimitĂ©e des donnĂ©es persistantes.


En rĂšgle gĂ©nĂ©rale, un message externe commence par une signature 256 bits (si nĂ©cessaire), une requĂȘte req-seqno 32 bits (si nĂ©cessaire), une expiration 32 bits (si nĂ©cessaire), et Ă©ventuellement une opĂ©ration 32 bits et d'autres paramĂštres nĂ©cessaires dans selon op . Le modĂšle de message externe ne doit pas ĂȘtre aussi standardisĂ© que le modĂšle de message interne, car les messages externes ne sont pas utilisĂ©s pour l'interaction entre diffĂ©rents contrats intelligents (Ă©crits par diffĂ©rents dĂ©veloppeurs et gĂ©rĂ©s par diffĂ©rents propriĂ©taires).


Obtenir des méthodes


Certains contrats intelligents devraient implĂ©menter certaines mĂ©thodes get bien dĂ©finies. Par exemple, tout contrat intelligent de rĂ©solution DNS pour TON DNS devrait implĂ©menter la mĂ©thode get dnsresolve. Les contrats intelligents personnalisĂ©s peuvent dĂ©finir leurs mĂ©thodes d'obtention spĂ©cifiques. Notre seule recommandation gĂ©nĂ©rale pour le moment est d'implĂ©menter la mĂ©thode get "seqno" (sans paramĂštres), qui renvoie le seqno actuel du contrat intelligent, qui utilise des numĂ©ros de sĂ©quence pour empĂȘcher les attaques de lecture associĂ©es aux mĂ©thodes externes entrantes chaque fois qu'une telle mĂ©thode a sens.


Dictionnaire:


  • Cellule - Une cellule TVM se compose d'au plus 1023 bits de donnĂ©es et d'au plus quatre rĂ©fĂ©rences Ă  d'autres cellules. Toutes les donnĂ©es persistantes (y compris le code TVM) dans la blockchain TON sont reprĂ©sentĂ©es comme une collection de cellules TVM (cf. [1, 2.5.14]). - une cellule TVM ne comprend pas plus de 1 023 bits de donnĂ©es et pas plus de quatre liens vers d'autres cellules. Toutes les donnĂ©es persistantes (y compris le code TVM) dans la blockchain TON sont prĂ©sentĂ©es comme un ensemble de cellules TVM (cf. [1, 2.5.14]). - extrait de la description de la machine virtuelle TON ( https://test.ton.org/tvm.pdf )

Quelles conclusions peut-on tirer sur la base de ce que j'ai lu?


  1. Vous pouvez envoyer des messages externes aux contrats pour déclencher une action.
  2. Attaques - il y a, par exemple, des attaques par rejeu
  3. Cela vaut la peine d’ utiliser la mĂ©thode seqno pour se protĂ©ger contre les attaques par rejeu.
  4. Les résolveurs DNS ont la méthode dnsresolve
  5. Vous pouvez stocker des hachages de messages externes pour vous protéger contre les attaques, mais vous devez les supprimer à temps, pour cela, il vaut la peine d'utiliser le champ expired_at pour les messages externes
  6. Les messages de non-retour ne sont nécessaires que pour créer des contrats; sinon, tous les messages internes sont renvoyés
  7. Les messages de demande-réponse doivent contenir les champs suivants: op, query_id - facultatif, et certains autres en fonction de la valeur de op
  8. Vous pouvez joindre des commentaires de texte au format UTF-8 pour les personnes et des «commentaires binaires» pour une lecture et un traitement automatiques par un logiciel tiers.
  9. Cela vaut la peine de gérer les exceptions et de le faire judicieusement
  10. "Message simple sans commentaires" - doit avoir un corps vide
  11. Le bit de poids fort des messages de réponse à la demande prend une valeur de 0 pour les messages de demande et une valeur de 1 pour les messages de réponse
  12. Il existe des valeurs opérationnelles standard pour les messages de réponse afin d'identifier les erreurs
  13. Si un message de rĂ©ponse est reçu avec une opĂ©ration inconnue, il doit ĂȘtre ignorĂ©, c'est-Ă -dire terminer l'exĂ©cution avec le code 0
  14. Vous devez payer pour envoyer des messages, pour le gaz et pour envoyer une rĂ©ponse. Dans le mĂȘme temps, s'il a envoyĂ© plus que nĂ©cessaire, l'excĂ©dent reviendra dans la rĂ©ponse.
  15. Lors de la réception de messages, il est toujours utile de vérifier d'abord le drapeau rebondi.

Merci de votre attention, je serai heureux de recevoir des commentaires constructifs!

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


All Articles