Este artigo é uma tradução de um documento publicado na página TON blockchain: smc-guidelines.txt . Talvez isso ajude alguém a dar um passo em direção ao desenvolvimento desta blockchain. Além disso, no final, fiz um breve resumo.
Mensagens internas
Os contratos inteligentes interagem entre si enviando as chamadas mensagens internas. Quando a mensagem interna atinge o destino especificado, uma transação regular é criada no nome da conta de destino e a mensagem interna é processada de acordo com o código especificado e os dados constantes dessa conta (contrato inteligente). Em particular, uma transação de processamento pode criar uma ou mais mensagens internas, algumas das quais podem ser endereçadas ao endereço de origem da mensagem interna que está sendo processada. Isso pode ser usado para criar “aplicativos cliente-servidor” simples quando uma solicitação é incorporada (encapsulada) em uma mensagem interna e enviada para outro contrato inteligente que processa a solicitação e envia a resposta de volta, novamente como uma mensagem interna.
Essa abordagem leva à necessidade de distinguir entre mensagens internas para "solicitação" e "resposta" (como uma "consulta" ou como uma "resposta"), ou que não exijam nenhum processamento adicional (como uma simples transferência de dinheiro). Além disso, quando uma resposta chega, deve haver uma maneira de entender a qual solicitação ela se relaciona.
Para atingir esse objetivo, é recomendável usar o seguinte modelo de mensagem interna (lembre-se de que o blockchain TON não impõe nenhuma restrição ao corpo da mensagem, ou seja, é apenas uma recomendação):
0) O corpo da mensagem pode ser incorporado na própria mensagem ou pode ser armazenado em uma célula separada (célula *), que é referenciada na mensagem, conforme indicado no fragmento TL-B do diagrama (em inglês, é mais fácil entender: ou armazenado em um separado célula referida na mensagem, conforme indicado pelo fragmento do esquema TL-B):
message$_ {X:Type} ... body:(Either X ^X) = Message X;
( https://core.telegram.org/mtproto - aqui você pode ler sobre esquemas TL)
O contrato inteligente de recebimento deve aceitar pelo menos mensagens internas com o corpo da mensagem incorporado (mesmo que elas sejam colocadas na célula que contém a mensagem - sempre que caberem na célula que contém a mensagem), não está muito claro o que isso significa, portanto, anexou o texto original). Se o contrato aceitar corpos de mensagens em células separadas (usando o construtor "correto" (Either X ^X)
), o processamento da mensagem recebida não deve depender do método específico de incorporação do corpo da mensagem. Por outro lado, é absolutamente legal não oferecer suporte ao corpo da mensagem em uma célula separada para simplificar solicitações e respostas.
1) O corpo da mensagem geralmente começa com os seguintes campos:
- op - número inteiro não assinado de 32 bits (big endian) que identifica a operação a ser executada ou o método de contrato inteligente a ser chamado.
- query_id é um número inteiro não assinado de 64 bits (big endian) usado em todas as mensagens internas de perguntas e respostas para identificar o relacionamento da resposta à solicitação (o query_id da resposta deve ser igual ao query_id da solicitação correspondente). Se op não for um método de solicitação-resposta (ele chama um método do qual nenhuma resposta é esperada), então query_id pode ser omitido.
- o restante do corpo da mensagem é específico para cada valor suportado do parâmetro op
2) Se op for zero, a mensagem será uma mensagem de transferência simples com um comentário. O comentário está contido no restante da mensagem (sem query_id e assim por diante, ou seja, a partir do 5º byte (explicação: se query_id não estiver , o campo op ocupa os primeiros 4 bytes)). Se não começar com o byte 0xff, o comentário será um texto;); ele pode ser exibido ao usuário final da carteira "como está" (após filtrar caracteres inválidos e de controle e verificar se essa é uma sequência UTF-8 válida). Por exemplo, os usuários podem especificar a finalidade de uma transferência simples da carteira para a carteira de outro usuário nesse campo. Por outro lado, se um comentário começa com o byte 0xff, o restante da mensagem é um "comentário binário" que não deve ser exibido ao usuário final como texto (apenas como um dump hexadecimal, se necessário). O uso proposto de comentários binários, por exemplo, é conter um identificador de pagamento para pagamento na loja e ser gerado e processado automaticamente pelo software da loja.
A maioria dos contratos inteligentes não precisa executar ações não triviais ou rejeitar uma mensagem recebida quando recebe uma "mensagem de transferência simples". Portanto, quando op é zero, a função de contrato inteligente para processar mensagens internas recebidas (geralmente chamadas recv_internal()
) deve sair imediatamente com o código 0, indicando sucesso (por exemplo, lançando a exceção 0 se um manipulador personalizado não estiver instalado no contrato inteligente) exceções). Isso levará ao fato de que o valor transferido pela mensagem será creditado na conta do destinatário sem nenhum efeito adicional.
3) "Uma transferência simples de mensagens sem comentários" possui um corpo vazio (mesmo sem o campo op ). As considerações acima se aplicam a essas mensagens. Observe que essas mensagens devem ter seu próprio corpo incorporado na célula da mensagem.
4) Esperamos que o campo op das mensagens de solicitação tenha o primeiro bit ("bit de ordem superior", traduzido como o primeiro, isso pode estar incorreto, mas, conforme explicado mais adiante, fica claro) está vazio, ou seja, o valor do campo deve estar no intervalo 1 .. 2^31-1
e, para mensagens de resposta, o primeiro bit (de ordem superior) deve ser igual a 1, ou seja, o valor do campo no intervalo 2^31 .. 2^32-1
. Se a mensagem não for uma solicitação nem uma resposta (o corpo não contém o parâmetro query_id ), deve conter o parâmetro op no intervalo como na mensagem de solicitação: 1 .. 2^31 - 1
.
5) Existem várias mensagens de resposta "padrão" para as quais op é 0xffffffff e 0xffffffffe. Em geral, os valores op de 0xfffffff0 a 0xffffffff são reservados para essas respostas padrão.
- op = 0xffffffff significa "operação não suportada". É seguido por um query_id de 64 bits extraído da consulta original e um op de 32 bits da consulta original. Todos os contratos inteligentes, exceto os mais simples, devem retornar esse erro quando receberem uma solicitação com uma operação desconhecida no intervalo 1 ... 2 ^ 31-1.
- op = 0xfffffffe significa "operação não permitida". É seguido pelo query_id de 64 bits da consulta original e, em seguida, pela operação de 32 bits extraída da consulta original.
Observe que "respostas" desconhecidas (com op no intervalo 2 ^ 31 ... 2 ^ 32-1) devem ser ignoradas (em particular, você não deve gerar uma resposta com op igual a 0xffffffff), bem como retorno inesperado ( mensagens devolvidas) (com o sinalizador "devolvido" definido).
Pagamento para processar solicitações e enviar respostas
Em geral, se um contrato inteligente deseja enviar uma solicitação a outro contrato inteligente, ele deve pagar pelo envio de uma mensagem interna para o contrato inteligente de destino (taxas de encaminhamento de mensagens), pelo processamento dessa mensagem no destino (taxa de gás: taxas de gás) e por enviar uma resposta, se necessário (taxas de encaminhamento de mensagens).
Na maioria dos casos, o remetente anexará uma pequena quantidade de grama à mensagem interna (por exemplo, 1 grama) (o suficiente para pagar pelo processamento dessa mensagem) e definirá o sinalizador de rejeição (ou seja, enviará uma mensagem interna insuficiente); o destinatário retornará a parte não utilizada do valor recebido com a resposta (subtraindo a taxa pelo envio da mensagem). Isso geralmente é conseguido chamando SENDRAWMSG com mode = 64 (consulte o Apêndice A da documentação da VM TON).
Se o destinatário não puder processar a mensagem recebida e a execução terminar com um código de saída diferente de zero (por exemplo, devido a uma exceção de desserialização de célula não tratada), a mensagem será automaticamente "devolvida" de volta ao remetente, e o sinalizador "rejeição" será desmarcado e definido. sinalize "devolvido". O corpo da mensagem devolvida será igual à mensagem original; portanto, é importante verificar o sinalizador "devolvido" da mensagem interna recebida antes de analisar o campo op no contrato inteligente e processar a solicitação correspondente (caso contrário, existe o risco de que a solicitação contida na mensagem devolvida seja processada pelo remetente original como uma nova solicitação separada). Se o sinalizador "devolvido" estiver definido, o código especial poderá entender qual solicitação falhou (por exemplo, desserializando op e query_id de uma mensagem devolvida) e tomar as medidas apropriadas. Um contrato inteligente mais simples pode simplesmente ignorar todas as mensagens retornadas (termine com um código de saída zero se o sinalizador "devolvido" estiver definido).
Por outro lado, o receptor pode analisar com êxito a solicitação recebida e descobrir que o método op solicitado não é suportado ou que outra condição de erro foi atendida. Em seguida, uma resposta com op igual a 0xffffffff ou outro valor apropriado deve ser enviada de volta usando SENDRAWMSG com mode = 64, conforme mencionado acima.
Em algumas situações, o remetente deseja transferir uma certa quantia de dinheiro ao mesmo tempo? para o remetente? (aparentemente, aqui, um erro e foi feito para o "destinatário") e recebe uma confirmação ou uma mensagem de erro. Por exemplo, um contrato inteligente de eleição de validador recebe uma solicitação para participar de uma eleição juntamente com uma oferta como um valor agregado. Nesses casos, faz sentido anexar, digamos, um grama extra ao valor estimado [custo] (aqui a palavra valor é usada em todos os lugares, no sentido de pagamento por alguma ação, então usei a palavra "custo"). Se ocorrer um erro (por exemplo, o lance não pode ser aceito por qualquer motivo), o valor total recebido (menos a taxa de processamento) deve ser devolvido ao remetente junto com a mensagem de erro (por exemplo, usando SENDRAWMSG com mode = 64, conforme descrito acima). Se for bem-sucedida, uma mensagem de confirmação é criada e exatamente um grama é enviado de volta (a taxa de transferência de mensagens é subtraída desse valor; este é o modo = 1 de SENDRAWMSG).
Usando mensagens não-saltáveis
Quase todas as mensagens internas enviadas entre contratos inteligentes devem ser retornadas (você pode traduzi-lo como "devolvido", mas para não ficar confuso, é mais fácil usar essa terminologia), ou seja, eles devem ter o bit "devolvido" não vazio. Então, se o contrato inteligente de destino não existir, ou se criar uma exceção não tratada ao processar esta mensagem, a mensagem será "retornada", suportando o restante do custo inicial (valor) (menos todas as taxas de transmissão de mensagens e gás). A mensagem retornada terá o mesmo corpo, mas com o sinalizador "devolver" limpo e o sinalizador "devolvido" definido. Portanto, todos os contratos inteligentes devem verificar o sinalizador "devolvido" de todas as mensagens recebidas e recebê-las silenciosamente (terminando imediatamente com um código de saída zero) ou executar algum processamento especial para determinar qual solicitação de saída falhou. A solicitação contida no corpo da mensagem retornada nunca deve ser executada.
Em alguns casos, devem ser usadas mensagens internas não reembolsáveis. Por exemplo, uma nova conta não pode ser criada sem pelo menos uma mensagem interna irrevogável enviada a ela. Se esta mensagem não contiver StateInit com o código e os dados do novo contrato inteligente, não faz sentido ter um corpo não vazio em uma mensagem interna que não retorna.
É uma boa idéia não permitir que o usuário final (por exemplo, carteira) envie mensagens irrevogáveis que carregam uma grande quantidade (por exemplo, mais de cinco gramas) ou pelo menos avise-os se tentarem fazer isso. É melhor enviar uma pequena quantia primeiro, depois criar um novo contrato inteligente e depois enviar uma quantia maior.
Mensagens Externas
Mensagens externas são enviadas externamente para contratos inteligentes localizados no blockchain TON para forçá-los a executar determinadas ações. Por exemplo, o contrato inteligente da carteira espera receber mensagens externas contendo pedidos (por exemplo, mensagens internas que serão enviadas do contrato inteligente da carteira) assinadas pelo proprietário da carteira; quando uma mensagem externa é recebida pelo contrato inteligente da carteira, ela primeiro verifica a assinatura, depois recebe a mensagem (iniciando a primitiva TVM ACCEPT) e executa todas as ações necessárias.
Observe que todas as mensagens externas devem ser protegidas contra ataques de repetição. Os validadores normalmente removem uma mensagem externa do pool de mensagens externas propostas (recebidas da rede); no entanto, em algumas situações, outro validador pode processar a mesma mensagem externa duas vezes (criando assim uma segunda transação para a mesma mensagem externa, o que leva à duplicação da ação original). Pior ainda, um invasor pode extrair uma mensagem externa de um bloco que contém uma transação de processamento e reenviá-la mais tarde. Isso pode causar, por exemplo, um contrato de carteira inteligente para repetir o pagamento.
A maneira mais fácil de proteger contratos inteligentes contra ataques de sniffing associados a mensagens externas é armazenar o contador cur-seqno de 32 bits nos dados constantes do contrato inteligente e aguardar o valor req-seqno na (parte assinada) de todas as mensagens externas recebidas. Em seguida, a mensagem externa será aceita (ACCEPTed - uma dica do ACCEPT primitivo) somente se a assinatura for válida e req-seqno for igual a cur-seqno . Após o processamento bem-sucedido, o valor de cur-seqno nos dados persistentes aumenta em um, para que a mesma mensagem externa nunca seja recebida novamente.
Você também pode incluir o campo expirar em em uma mensagem externa e aceitar a mensagem somente se o tempo atual do Unix for menor que o valor desse campo. Essa abordagem pode ser usada em combinação com seqno ; Como alternativa, o contrato inteligente de recebimento pode armazenar um conjunto (hashes) de todas as últimas mensagens externas recebidas (não expiradas) em seus dados permanentes e rejeitar uma nova mensagem externa se for uma duplicata de uma das mensagens salvas. Você também deve implementar a coleta e remoção de mensagens expiradas neste conjunto para evitar o crescimento ilimitado de dados persistentes.
Como regra, uma mensagem externa começa com uma assinatura de 256 bits (se necessário), um req-seqno de 32 bits (se necessário), um prazo de expiração de 32 bits (se necessário) e, possivelmente, um op de 32 bits e outros parâmetros necessários em dependendo da op . O modelo de mensagem externa não precisa ser tão padronizado quanto o modelo de mensagem interna, pois as mensagens externas não são usadas para interação entre diferentes contratos inteligentes (gravados por diferentes desenvolvedores e gerenciados por diferentes proprietários).
Obter métodos
Espera-se que alguns contratos inteligentes implementem certos métodos get bem definidos. Por exemplo, espera-se que qualquer contrato inteligente do resolvedor dns para DNS TON implemente o método dnsresolve get. Contratos inteligentes personalizados podem definir seus métodos get específicos. Nossa única recomendação geral no momento é implementar o método get "seqno" (sem parâmetros), que retorna o seqno atual do contrato inteligente, que usa números de sequência para impedir ataques de reprodução associados a métodos externos sempre que esse método significado.
Dicionário:
- Célula - Uma célula TVM consiste em no máximo 1023 bits de dados e no máximo em quatro referências a outras células. Todos os dados persistentes (incluindo o código TVM) no Blockchain da TON são representados como uma coleção de células TVM (cf. [1, 2.5.14]). - uma célula TVM consiste em não mais que 1023 bits de dados e não mais que quatro links para outras células. Todos os dados persistentes (incluindo o código TVM) no blockchain TON são apresentados como um conjunto de células TVM (cf. [1, 2.5.14]). - trecho da descrição da TON Virtual Machine ( https://test.ton.org/tvm.pdf )
Que conclusões podem ser tiradas com base no que li?
- Você pode enviar mensagens externas para contratos para acionar uma ação.
- Ataques - existem, por exemplo, ataques de repetição
- Vale a pena fazer o método seqno para se proteger contra ataques de repetição.
- Os resolvedores de DNS têm o método dnsresolve
- Você pode armazenar hashes de mensagens externas para se proteger contra ataques, mas precisa excluí-las a tempo, pois vale a pena usar o campo expired_at para mensagens externas
- As mensagens de não retorno são necessárias apenas para criar contratos; caso contrário, todas as mensagens internas são de retorno
- As mensagens de solicitação-resposta devem conter os seguintes campos: op, query_id - opcional e mais alguns, dependendo do valor de op
- Você pode anexar comentários de texto no formato UTF-8 para pessoas e "comentários binários" para leitura e processamento automáticos por software de terceiros.
- Vale a pena lidar com exceções e fazê-lo com sabedoria
- "Mensagem simples sem comentários" - deve ter um corpo vazio
- O bit de ordem superior das mensagens de resposta à solicitação leva um valor de 0 para mensagens de solicitação e um valor de 1 para mensagens de resposta
- Existem valores operacionais padrão para mensagens de resposta para identificar erros
- Se uma mensagem de resposta for recebida com um op desconhecido, ela deverá ser ignorada, ou seja, execução completa com o código 0
- Você tem que pagar pelo envio de mensagens, pelo gás e pelo envio de uma resposta. Ao mesmo tempo, se ele enviou mais do que o necessário, o excesso retornará na resposta.
- Ao receber mensagens, sempre vale a pena verificar a bandeira devolvida primeiro.
Obrigado por sua atenção, terei prazer em receber feedback construtivo!