Este artículo es una traducción de un documento publicado en la página TON blockchain: smc-Guidelines.txt . Quizás esto ayude a alguien a dar un paso hacia el desarrollo de esta cadena de bloques. Además, al final hice un breve resumen.
Mensajes internos
Los contratos inteligentes interactúan entre sí enviando los llamados mensajes internos. Cuando el mensaje interno llega a su destino especificado, se crea una transacción regular a nombre de la cuenta de destino, y el mensaje interno se procesa de acuerdo con el código especificado y los datos constantes de esta cuenta (contrato inteligente). En particular, una transacción de procesamiento puede crear uno o más mensajes internos, algunos de los cuales pueden dirigirse a la dirección de origen del mensaje interno que se está procesando. Esto se puede usar para crear "aplicaciones cliente-servidor" simples cuando una solicitud se incrusta (encapsula) en un mensaje interno y se envía a otro contrato inteligente que procesa la solicitud y devuelve la respuesta, nuevamente como un mensaje interno.
Este enfoque lleva a la necesidad de distinguir entre mensajes internos a la "solicitud" y la "respuesta" (como una "consulta" o como una "respuesta"), o que no requieren ningún procesamiento adicional (como una simple transferencia de dinero). Además, cuando llega una respuesta, debe haber una manera de entender a qué solicitud se refiere.
Para lograr este objetivo, se recomienda utilizar la siguiente plantilla de mensaje interno (recuerde que la cadena de bloques TON no impone ninguna restricción en el cuerpo del mensaje, es decir, es solo una recomendación):
0) El cuerpo del mensaje puede incrustarse en el mensaje en sí, o puede almacenarse en una celda separada (celda *), a la que se hace referencia en el mensaje, como se indica en el fragmento TL-B del diagrama (en inglés es más fácil de entender: o se almacena en una celda separada celda a la que se hace referencia desde el mensaje, como lo indica el fragmento del esquema TL-B):
message$_ {X:Type} ... body:(Either X ^X) = Message X;
( https://core.telegram.org/mtproto - aquí puedes leer sobre esquemas TL)
El contrato inteligente receptor debe aceptar al menos mensajes internos con el cuerpo del mensaje incrustado (incluso si se colocan en la celda que contiene el mensaje, siempre que encajen en la celda que contiene el mensaje, no está muy claro qué significa esto, por lo tanto, adjunta el texto original). Si el contrato acepta cuerpos de mensajes en celdas separadas (usando el constructor "correcto" (Either X ^X)
), el procesamiento del mensaje entrante no debe depender del método particular de incrustar el cuerpo del mensaje. Por otro lado, es absolutamente legal no admitir el cuerpo del mensaje en una celda separada para simplificar las solicitudes y respuestas.
1) El cuerpo del mensaje generalmente comienza con los siguientes campos:
- op : entero sin signo de 32 bits (big-endian) que identifica la operación a ejecutar o el método de contrato inteligente a llamar.
- query_id es un entero sin signo de 64 bits (big-endian) utilizado en todos los mensajes internos de preguntas y respuestas para identificar la relación de la respuesta a la solicitud (el query_id de la respuesta debe ser igual al query_id de la solicitud correspondiente). Si op no es un método de solicitud-respuesta (llama a un método del que no se espera respuesta), se puede omitir query_id .
- el resto del cuerpo del mensaje es específico para cada valor admitido del parámetro op
2) Si op es cero, entonces el mensaje es un simple mensaje de transferencia con un comentario. El comentario está contenido en el resto del mensaje (sin query_id y así sucesivamente, es decir, a partir del quinto byte (explicación: si query_id no lo está , entonces el campo op ocupa los primeros 4 bytes)). Si no comienza con el byte 0xff, el comentario es de texto;); se puede mostrar al usuario final de la billetera "tal cual" (después de filtrar caracteres no válidos y de control y verificar que se trata de una cadena UTF-8 válida). Por ejemplo, los usuarios pueden especificar el propósito de una simple transferencia de su billetera a la billetera de otro usuario en este campo. Por otro lado, si un comentario comienza con el byte 0xff, el resto del mensaje es un "comentario binario" que no debe mostrarse al usuario final como texto (solo si es necesario un volcado hexadecimal). El uso propuesto de comentarios binarios, por ejemplo, es contener un identificador de pago para el pago en la tienda y ser generado y procesado automáticamente por el software de la tienda.
La mayoría de los contratos inteligentes no tienen que realizar acciones no triviales o rechazar un mensaje entrante cuando reciben un "mensaje de transferencia simple". Por lo tanto, cuando op resulta ser cero, la función de contrato inteligente para procesar mensajes internos entrantes (generalmente llamada recv_internal()
) debería salir inmediatamente con el código 0, lo que indica éxito (por ejemplo, arrojando la excepción 0 si no se instala un controlador personalizado en el contrato inteligente excepciones). Esto conducirá al hecho de que la cantidad transferida por el mensaje se acreditará a la cuenta del destinatario sin ningún otro efecto.
3) "Una transferencia de mensaje simple sin comentarios" tiene un cuerpo vacío (incluso sin el campo op ). Las consideraciones anteriores se aplican a dichos mensajes. Tenga en cuenta que dichos mensajes deben tener su propio cuerpo incrustado en la celda del mensaje.
4) Esperamos que el campo op de los mensajes de solicitud tenga el primer bit ("bit de orden superior", traducido como el primero, esto puede ser incorrecto, pero como se explica más adelante queda claro) está vacío, es decir, el valor del campo debe estar en el rango 1 .. 2^31-1
, y para los mensajes de respuesta, el primer bit (de orden superior) debe ser igual a 1, es decir, el valor del campo en el rango 2^31 .. 2^32-1
. Si el mensaje no es una solicitud ni una respuesta (el cuerpo no contiene el parámetro query_id ), entonces debe contener el parámetro op en el rango como en el mensaje de solicitud: 1 .. 2^31 - 1
.
5) Hay varios mensajes de respuesta "estándar" para los cuales op es 0xffffffff y 0xffffffffe. En general, los valores op de 0xfffffff0 a 0xffffffff están reservados para tales respuestas estándar.
- op = 0xffffffff significa "la operación no es compatible". Le sigue un query_id de 64 bits extraído de la consulta original y una operación de 32 bits de la consulta original. Todos menos los contratos inteligentes más simples deberían devolver este error cuando reciben una solicitud con un operador desconocido en el rango 1 ... 2 ^ 31-1.
- op = 0xfffffffe significa "operación no permitida". Le sigue el query_id de 64 bits de la consulta original, y luego la operación de 32 bits extraída de la consulta original.
Tenga en cuenta que las "respuestas" desconocidas (con op en el rango 2 ^ 31 ... 2 ^ 32-1) deben ignorarse (en particular, no debe generar una respuesta con op igual a 0xffffffff), así como un retorno inesperado ( (rebotado) -mensajes (con el conjunto de banderas "rebotado").
Pago por procesar solicitudes y enviar respuestas
En general, si un contrato inteligente desea enviar una solicitud a otro contrato inteligente, debe pagar por enviar un mensaje interno al contrato inteligente objetivo (tarifas de reenvío de mensajes), para procesar este mensaje en el destino (tarifa de gas: tarifas de gas) y para enviar una respuesta si es necesario (tarifas de reenvío de mensajes).
En la mayoría de los casos, el remitente adjuntará una pequeña cantidad de gramo al mensaje interno (por ejemplo, 1 gramo) (suficiente para pagar el procesamiento de este mensaje) y establecerá el indicador de "rebote" en él (es decir, enviará un mensaje interno rebotable); el destinatario devolverá la parte no utilizada del valor recibido con la respuesta (restando la tarifa por enviarle el mensaje). Esto generalmente se logra llamando a SENDRAWMSG con mode = 64 (véase el Apéndice A de la documentación de TON VM).
Si el destinatario no puede procesar el mensaje recibido y la ejecución finaliza con un código de salida distinto de cero (por ejemplo, debido a una excepción de deserialización de celda no controlada), el mensaje será "devuelto" automáticamente al remitente, y la bandera de "rebote" será desmarcada y configurada. bandera "rebotada". El cuerpo del mensaje devuelto será el mismo que el mensaje original; por lo tanto, es importante verificar el indicador "devuelto" del mensaje interno entrante antes de analizar el campo operativo en el contrato inteligente y procesar la solicitud correspondiente (de lo contrario, existe el riesgo de que la solicitud contenida en el mensaje devuelto sea procesada por su remitente original como una nueva solicitud por separado). Si se establece el indicador "devuelto", un código especial puede comprender qué solicitud falló (por ejemplo, deserializando op y query_id de un mensaje devuelto) y tomar las medidas apropiadas. Un contrato inteligente más simple puede simplemente ignorar todos los mensajes devueltos (finalizar con un código de salida cero si se establece el indicador "rebotado").
Por otro lado, el receptor puede analizar con éxito la solicitud entrante y descubrir que el método operativo solicitado no es compatible o que se ha cumplido otra condición de error. Luego, se debe enviar una respuesta con op igual a 0xffffffff u otro valor apropiado utilizando SENDRAWMSG con mode = 64, como se mencionó anteriormente.
En algunas situaciones, ¿el remitente desea transferir una cierta cantidad de dinero al mismo tiempo? al remitente? (aquí, aparentemente, un error, y estaba destinado al "destinatario") y recibe una confirmación o un mensaje de error. Por ejemplo, un contrato inteligente de elecciones de validador recibe una solicitud para participar en una elección junto con una oferta como valor agregado. En tales casos, tiene sentido adjuntar, digamos, un gramo adicional al valor estimado [costo] (Aquí la palabra valor se usa en todas partes, en el significado del pago de alguna acción, así que usé la palabra "costo"). Si se produce un error (por ejemplo, la oferta no puede aceptarse por ningún motivo), el monto total recibido (menos la tarifa de procesamiento) debe devolverse al remitente junto con el mensaje de error (por ejemplo, utilizando SENDRAWMSG con mode = 64, como se describe arriba). Si tiene éxito, se crea un mensaje de confirmación y se devuelve exactamente un gramo (en este caso, la tarifa de transferencia del mensaje se resta de este valor; este es el modo = 1 de SENDRAWMSG).
Usar mensajes no rebotables
Casi todos los mensajes internos enviados entre contratos inteligentes deben devolverse (puede traducirlo como "rebote", pero para no confundirse, es más fácil usar esta terminología), es decir, deben tener el bit "rebote" no vacío. Luego, si el contrato inteligente objetivo no existe, o si crea una excepción no controlada al procesar este mensaje, el mensaje será "devuelto", asumiendo el resto del costo inicial (valor) (menos todas las tarifas por la transmisión de mensajes y gas). El mensaje devuelto tendrá el mismo cuerpo, pero con la bandera de "rebote" borrada y la bandera de "rebote" establecida. Por lo tanto, todos los contratos inteligentes deben verificar el indicador de "rebote" de todos los mensajes entrantes y recibirlos silenciosamente (terminando inmediatamente con un código de salida cero) o realizar un procesamiento especial para determinar qué solicitud saliente falló. La solicitud contenida en el cuerpo del mensaje devuelto nunca debe ejecutarse.
En algunos casos, se deben usar mensajes internos no rebotables. Por ejemplo, no se puede crear una nueva cuenta sin que se le envíe al menos un mensaje interno irrevocable. Si este mensaje no contiene StateInit con el código y los datos del nuevo contrato inteligente, no tiene sentido tener un cuerpo no vacío en un mensaje interno no devuelto.
Es una buena idea no permitir que el usuario final (p. Ej., Billetera) envíe mensajes irrevocables que llevan una gran cantidad (p. Ej., Más de cinco gramos), o al menos advertirles si intentan hacerlo. Es mejor enviar una pequeña cantidad primero, luego crear un nuevo contrato inteligente y luego enviar una cantidad mayor.
Mensajes externos
Los mensajes externos se envían externamente a contratos inteligentes ubicados en la cadena de bloques TON para obligarlos a realizar ciertas acciones. Por ejemplo, el contrato inteligente de la billetera espera recibir mensajes externos que contengan pedidos (por ejemplo, mensajes internos que se enviarán desde el contrato inteligente de la billetera) firmados por el propietario de la billetera; cuando el contrato inteligente de la billetera recibe dicho mensaje externo, primero verifica la firma, luego recibe el mensaje (al lanzar la primitiva ACEPTAR TVM) y luego realiza todas las acciones necesarias.
Tenga en cuenta que todos los mensajes externos deben estar protegidos contra ataques de reproducción. Los validadores generalmente eliminan un mensaje externo del conjunto de mensajes externos propuestos (recibidos de la red); sin embargo, en algunas situaciones, otro validador puede procesar el mismo mensaje externo dos veces (creando así una segunda transacción para el mismo mensaje externo, lo que conduce a la duplicación de la acción original). Peor aún, un atacante puede extraer un mensaje externo de un bloque que contiene una transacción de procesamiento y reenviarlo más tarde. Esto puede causar, por ejemplo, un contrato de billetera inteligente para repetir el pago.
La forma más fácil de proteger los contratos inteligentes de los ataques de rastreo asociados con mensajes externos es almacenar el contador cur-seqno de 32 bits en los datos constantes del contrato inteligente y esperar el valor req-seqno en la (parte firmada) de cualquier mensaje externo entrante. Luego se acepta el mensaje externo (ACEPTADO - una pista de la ACEPTACIÓN primitiva) solo si la firma es válida y req-seqno es igual a cur-seqno . Después de un procesamiento exitoso, el valor de cur-seqno en los datos persistentes aumenta en uno, por lo que el mismo mensaje externo nunca será recibido nuevamente.
También puede incluir el campo expirar en un mensaje externo y aceptar el mensaje solo si el tiempo actual de Unix es menor que el valor de este campo. Este enfoque se puede usar en combinación con seqno ; alternativamente, el contrato inteligente receptor puede almacenar un conjunto (hashes) de todos los últimos mensajes externos recibidos (no caducados) en sus datos permanentes y rechazar un nuevo mensaje externo si es un duplicado de uno de los mensajes guardados. También debe implementar la recopilación y eliminación de mensajes caducados en este conjunto para evitar el crecimiento ilimitado de datos persistentes.
Como regla general, un mensaje externo comienza con una firma de 256 bits (si es necesario), un req-seqno de 32 bits (si es necesario), una expiración de 32 bits (si es necesario) y posiblemente una operación de 32 bits y otros parámetros necesarios en dependiendo de op . La plantilla de mensaje externo no tiene que estar tan estandarizada como la plantilla de mensaje interno, ya que los mensajes externos no se utilizan para la interacción entre diferentes contratos inteligentes (escritos por diferentes desarrolladores y administrados por diferentes propietarios).
Obtener métodos
Se espera que algunos contratos inteligentes implementen ciertos métodos get bien definidos. Por ejemplo, se espera que cualquier contrato inteligente dns resolver para TON DNS implemente el método dnsresolve get. Los contratos inteligentes personalizados pueden definir sus métodos de obtención específicos. Nuestra única recomendación general en este momento es implementar el método de obtención "seqno" (sin parámetros), que devuelve la seqno actual del contrato inteligente, que utiliza números de secuencia para evitar ataques de reproducción asociados con métodos externos entrantes siempre que dicho método tenga significado.
Diccionario:
- Celda: una celda TVM consta de como máximo 1023 bits de datos y como máximo cuatro referencias a otras celdas. Todos los datos persistentes (incluido el código TVM) en TON Blockchain se representan como una colección de células TVM (cf. [1, 2.5.14]). - una celda TVM consta de no más de 1023 bits de datos y no más de cuatro enlaces a otras celdas. Todos los datos persistentes (incluido el código TVM) en la cadena de bloques TON se presentan como un conjunto de celdas TVM (cf. [1, 2.5.14]). - extracto de la descripción de la máquina virtual TON ( https://test.ton.org/tvm.pdf )
¿Qué conclusiones se pueden sacar de lo que leo?
- Puede enviar mensajes externos a contratos para activar una acción.
- Ataques: hay, por ejemplo, ataques de repetición
- Vale la pena hacer el método seqno para proteger contra ataques de repetición.
- Los solucionadores de Dns tienen el método dnsresolve
- Puede almacenar hashes de mensajes externos para protegerse contra ataques, pero debe eliminarlos a tiempo, para esto vale la pena usar el campo expired_at para mensajes externos
- Los mensajes sin devolución solo son necesarios para crear contratos; de lo contrario, todos los mensajes internos son devueltos
- Los mensajes de solicitud-respuesta deben contener los siguientes campos: op, query_id - opcional y algunos más dependiendo del valor de op
- Puede adjuntar comentarios de texto en formato UTF-8 para personas y "comentarios binarios" para lectura y procesamiento automáticos por software de terceros.
- Vale la pena manejar excepciones y hacerlo sabiamente
- "Mensaje simple sin comentarios": debe tener un cuerpo vacío
- El bit de orden superior de los mensajes de solicitud-respuesta toma un valor de 0 para los mensajes de solicitud y un valor de 1 para los mensajes de respuesta
- Hay valores operativos estándar para mensajes de respuesta para identificar errores
- Si se recibe un mensaje de respuesta con una operación desconocida, se debe ignorar, es decir, completar la ejecución con el código 0
- Tiene que pagar por enviar mensajes, por gas y por enviar una respuesta. Al mismo tiempo, si envió más de lo necesario, el exceso volverá en la respuesta.
- Al recibir mensajes, siempre vale la pena verificar primero el indicador rebotado.
¡Gracias por su atención, me complacerá recibir comentarios constructivos!