Publicación, Normalización, Verificación y Difusión de Transacciones en Nodos ADAMANT
Funciones
Transaction.prototype.verify verifica una transacción recuperada de otros bloques, que puede incluir transacciones antiguas. Transactions.prototype.publish verifica transacciones entrantes de usuarios, comprobando transacciones no confirmadas recientes. verify también se llama tras recibir una nueva transacción desde la API, después de los métodos publish y objectNormalize. publish solo se llama al enviar transacciones mediante la API pública.
Procesamiento de Transacciones
Cuando un nodo recibe una nueva transacción desde una aplicación cliente mediante la API como primer nodo en verla, Logic->Transaction.prototype.publish crea una nueva transacción a partir de los datos recibidos, valida las propiedades de la transacción del cliente (incluyendo timestampMs), añade un ID de transacción y procesa la transacción no confirmada como de costumbre. Si un nodo recibe una nueva transacción de otro par en la altura actual, valida la transacción y elimina al par si es inválida, luego procesa la transacción no confirmada. Al obtener una transacción de otro par durante el arranque o al verificar la blockchain desde la altura 0, el nodo valida la transacción, elimina al par si es inválida y procesa la transacción no confirmada mientras verifica múltiples transacciones simultáneamente. La validación implica que el nodo llama a objectNormalize() para la validación del esquema de la transacción recibida.
El flujo de procesamiento de transacciones no confirmadas comienza con logic.transaction.process(), que verifica el ID de la transacción y normaliza el ID del remitente. A continuación, la transacción se normaliza: logic.transaction.objectNormalize() valida el objeto de transacción contra un esquema y elimina propiedades innecesarias, incluyendo timestampMs antes de la activación de spaceship. Finalmente, se llama a logic.transaction.verify() para verificar todas las propiedades como timestamp, timestampMs y signature.
Eliminar un par significa quitarlo de la lista de pares hasta que sea redescubierto; los nodos no bloquean pares, por lo que el estado BANNED nunca se aplica. Un nodo difunde una transacción a otros nodos tras procesar la transacción no confirmada. Además de objectNormalize(), el nodo tiene normalize(), que solo se usa para el endpoint POST /transactions/normalize, el cual debería quedar obsoleto. El nodo tiene tanto apply() como applyUnconfirmed() porque los métodos “apply” cambian los saldos de las cuentas del remitente y destinatario, mientras que applyUnconfirmed() cambia el saldo no confirmado; lo mismo aplica a los métodos “undo”. Cuando un nodo genera un nuevo bloque, las transacciones se toman de una lista ya verificada. Actualmente, publish() verifica si la marca de tiempo de la transacción está en el futuro o más de constants.maxTransactionAgeSec segundos en el pasado. No tiene sentido añadir las mismas comprobaciones en publish que en verify, porque esta última debe llamarse después de publish. Además, las comprobaciones de publish no pueden incluirse en verify porque dependen del tiempo actual, lo que hace imposible verificar si una transacción antigua tiene menos de 5 segundos. Una aplicación cliente recibirá mensajes de error como The difference between timestamp and timestampMs is greater than ${maxTimestampMsDelta}ms. o Invalid transaction timestamp. Timestamp is not in the int32 range si estas comprobaciones no están en publish() sino en verify().
Notas sobre Marcas de Tiempo
La marca de tiempo timestamp de una transacción es el tiempo de época de ADAMANT en segundos, no tiempo Unix. La marca de tiempo timestampMs debe ser el tiempo de época de ADAMANT en milisegundos, no tiempo Unix; los milisegundos Unix pueden derivarse como constants.epochTime.getTime() + timestampMs. El campo timestampMs no forma parte de los bytes de la transacción, firmas, IDs de transacción ni hashes, incluso después de la activación de spaceship. Después de spaceship, la validación sensible al consenso debe exigir que timestampMs esté en el mismo segundo de época de ADAMANT que timestamp: 0 <= timestampMs - timestamp * 1000 < 1000. Esta regla más estricta del mismo segundo es intencional: timestampMs debe refinar exactamente el mismo segundo de época de ADAMANT almacenado en timestamp, y los clientes deben calcular timestamp = Math.floor(timestampMs / 1000). Usar Math.round() o Math.ceil() cerca del límite de un segundo crea un par inconsistente y debe rechazarse. Un análisis de clientes y fuentes actuales encontró que adamant-api-jsclient, la PWA, adamant-console (mediante [email protected]), iOS y ejemplos de documentación usan todos Math.floor o truncamiento equivalente, sin ninguna ruta de generación de marca de tiempo para transacciones ADM salientes que use round o ceil. Antes de spaceship, la normalización sensible al consenso debe eliminar timestampMs para que el comportamiento previo a la activación siga siendo compatible con nodos antiguos. Se permiten múltiples transacciones del mismo remitente dentro de un segundo siempre que sean transacciones distintas; timestampMs ayuda a los clientes a ordenar mensajes de chat rápidos, pero no debe convertirse en un nonce de transacción ni en entrada de firma.
Comprobaciones en el Momento de Admisión
El método publish() contiene comprobaciones de admisión de la API pública para transacciones recién enviadas, que pueden depender del reloj actual del nodo. Si una transacción pasa publish(), aún se procesa mediante verify(), que intencionalmente no tiene comprobación de tiempo actual y debe mantener solo comprobaciones deterministas como esquema, ID, firmas, saldos y la regla del mismo segundo para timestampMs. Las comprobaciones de tiempo actual de publish() no deben moverse a verify() porque verify() se usa para reproducción, sincronización y transacciones históricas. La comprobación existente de futuro está basada en ranuras: con ranuras de 5 segundos, un reloj de cliente adelantado unos 400 ms solo provoca Transaction timestamp is in the future cuando la marca de tiempo redondeada en segundos cruza a la siguiente ranura. maxTransactionFutureMs es una tolerancia de admisión de API pública no sensible al consenso, aplicada solo en publish(), no en reproducción, verificación de pares/bloques ni lógica de activación de consenso. La condición prevista preserva la antigua política de admisión basada en ranuras mientras suaviza el caso límite rechazando solo cuando transactionSlotNumber > currentSlotNumber y el tiempo de la transacción está más de maxTransactionFutureMs adelantado respecto al tiempo actual del nodo. Para este cálculo, use timestampMs si el cliente lo proporcionó; de lo contrario, use timestamp * 1000. Una pequeña tolerancia no sensible al consenso en publish() (por ejemplo, 500 ms para la siguiente ranura) puede mejorar la experiencia de usuario para relojes de cliente ligeramente adelantados sin cambiar la validación de reproducción, aunque las aplicaciones cliente aún deben mantener una pequeña reserva de marca de tiempo para compatibilidad con nodos antiguos. Una transacción cuyo timestamp esté un segundo adelantado respecto al bloque actual aún puede incluirse en ese bloque sin romper la blockchain, ya que la marca de tiempo de la transacción es metadato firmado, no la fuente de validez de ranura o bloque, y el consenso no exige tx.timestamp <= block.timestamp. Durante la reproducción, los nodos no comparan las marcas de tiempo de las transacciones con la hora local real, por lo que la tolerancia en el momento de admisión no puede causar divergencia en la reproducción.
Endpoint de Estado
El endpoint /api/node/status puede devolver campos adicionales de reloj como nodeTimestampMs (milisegundos de época ADAMANT) y unixTimestampMs sin impacto en el consenso. nodeTimestamp sigue siendo segundos de época ADAMANT.
Alturas de Activación
Las alturas de activación del consenso deben ser configurables para testnet y cadenas basadas en ADAMANT, manteniendo los valores predeterminados de mainnet en el código o configuración. La comprobación histórica fairSystemActivateBlock: 4359464 usaba height > fairSystemActivateBlock, por lo que la altura equivalente de activación para una activación nombrada fairSystem es 4.359.465. La activación de spaceship debe controlar solo el comportamiento sensible al consenso, como aceptar, normalizar, almacenar y validar timestampMs durante el procesamiento de bloques, pares y reproducción. Las comprobaciones relativas al reloj en publish() son política de admisión y deben permanecer fuera de la activación del consenso.