Esta página sirve para entender qué ocurre con una transacción después de que rand_sendTransaction la acepta y antes de que un bloque la incluya: por qué se rechazó, si es el caso; por qué puede desaparecer del pool sin haber fallado nunca; en qué orden se llena un bloque; y qué puede y qué no puede hacerle a un validador una avalancha de pruebas inválidas. El mempool es crates/randprotocol-node/src/mempool.rs; las salvaguardas que lo preceden están en admission.rs.
Sin remitentes, sin nonces
En una cadena de cuentas, el mempool ordena las transacciones de cada remitente por nonce. Una cadena blindada no tiene remitentes: un bundle no lleva dirección ni firma, y dos bundles solo están relacionados cuando tocan la misma nota. Por tanto, ya no queda ningún orden por remitente que mantener. Lo que sustituye a la contabilidad de nonces es el seguimiento de conflictos: el pool indexa los nullifiers y los compromisos de cada transacción pendiente, y rechaza a todo recién llegado que reclame alguno de ellos. Lo mismo vale para los pocos contadores de los que el libro mayor acepta exactamente una transacción por valor: el nonce de registro de un validador (un Unbond o un Withdraw), el mint_nonce de un token (compartido por TokenMint y SetAuthority), el índice del registro al que queda sujeto un RegisterToken, y los pause_nonce y list_nonce del puente. Cada uno es una ranura que, como máximo, una transacción del pool puede ocupar. Como mucho una de dos transacciones así podría llegar a incluirse, y conservar la otra le costaría al proponente espacio de bloque y a cada nodo una verificación de prueba por ronda de gossip.
El pool admite como máximo 10,000 transacciones (Mempool::new(10_000) en node.rs) y, desde v0.6.8, como máximo los bytes de ocho bloques: MAX_POOL_BLOCKS = 8 veces el max_block_bytes de la cadena, 160 MiB en la chain 20 (auditoría v6, CH-7). Antes solo se limitaba el número, mientras que una transacción con prueba pesa entre 1.3 MB y más de 12 MB. Cada una se valida contra el libro mayor en la punta del árbol de consenso, no en el último bloque confirmado: el estado sobre el que un proponente construiría realmente.
Admisión: lo barato antes que lo caro
Una transacción que llega por RPC o por gossip pasa por estos pasos en orden, y la primera negativa es el error que se ve:
- Duplicada. El hash ya está en el pool:
already in mempool. - Conflicto. Un nullifier, un compromiso o una de las ranuras de nonce e índice anteriores ya está reclamado por una transacción pendiente:
conflicts with a pending transaction over <hex>. Para una ranura de token, el hex es una clave de visualización: una palabra etiqueta (toknpara unmint_nonce,tidxpara un índice de registro), seguida del índice y del nonce. Una atestación del puente en la que dos relayers compitieron se distingue por su digest:conflicts with a pending transaction over bridge attestation <hash>. - Lleno.
mempool full. La comprobación de capacidad es la última de las tres. Al llegar al tope de bytes, un recién llegado puede ocupar el lugar de entradas del pool que queden estrictamente por debajo de él —por comisión por encima del mínimo actual de cada una por KiB, y luego por comisión—, expulsando primero las más baratas hasta que quepa; si eso no basta, o solo empata, es él quien se rechaza. Las acciones de gobernanza quedan fuera de ambos topes y nunca se expulsan: las cuatro acciones de gobernanza del puentePauseMints,UnpauseMints,RegisterBridgedToken,ListBackingy, desde v0.6.8,RotatePqGuardians,RotatePauseKey, sus formas…V2,CancelRotation,AdmitValidatorySlashEquivocation. Sus bytes y entradas siguen contando para los topes.PauseMintses el freno ante un compromiso de la clave de los guardianes: sin bundle, sin comisión, y la única transacción que debe llegar a un bloque mientras un atacante está acuñando, de modo que un pool lleno no debe dejarla fuera. La exención queda acotada por el paso de conflicto:pause_nonceylist_nonceadmiten cada uno una sola transacción en el pool a la vez, así que las cuatro acciones del puente pueden añadir como máximo dos entradas por encima del tope; las demás gastan cada una un nonce o un quórum propios. - La mitad barata de
Ledger::validate. Todo lo que puede quedar obsoleto entre la admisión y la inclusión y no requiere prueba: el ancla es una de las últimas 1,024 raíces en la chain 20 (proof_window_blocksdel génesis; 256 sin él),timeestá dentro de la ventana, los nullifiers no están gastados, los compromisos no existen, el digest de una atestación no está reclamado, el nonce de registro de una acción de staking está vigente, elmint_nonceo el índice de registro de una acción de token es el que tiene el registro (wrong mint nonce,wrong token indexuna vez que la cadena lo ha superado), y el nonce de una pausa o de un listado es el vigente del puente. Aquí también se calculan los compromisos que una transacción crea, incluida la nota que el propio libro mayor deriva para unWithdraw, unBridgeAttesto un pago a un agregador, que no viaja por la red pero tampoco debe colisionar. Los límites de tamaño se comprueban antes de decodificar nada: una prueba que supere elmax_proof_bytesde la cadena (2 MiB por defecto, 4 MiB en la chain 20), una atestación de más de 16 KiB o una transacción mayor que un bloque (max_block_bytes: 4 MiB por defecto, 20 MiB en la chain 20) se rechaza de plano, de modo que un blob sobredimensionado no obtiene ningún trabajo. Una prueba de bundle en la forma sellada (podada) se rechaza en este mismo paso fuera de la sincronización en forma sellada, comoPrunedFormOutsideSync. Después, sin verificar todavía nada, el pool lee las cabeceras de la prueba. Primero se aplican tres políticas: no entra en el pool una llamada cuya tabla de entrada, de keccak o de sha256 tenga menos de 2^7 filas (CallRevealsPrivateInputs), ni un despliegue que ninguna llamada podría sostener (ProgramUncallable), ni una transacción cuya prueba de bundle o de llamada tenga un campo de cabecera que el prover honesto no escribiría (NonCanonicalProof). Las dos últimas son también reglas del libro mayor en la chain 20, bajohardening_v6. Desde v0.6.8, una cabecera cuyo grado declarado supera el techo de la tabla más alta se rechaza comoNonCanonicalProofantes de que la comprobación la recorra (auditoría v6, ZKV-4), y un pánico en cualquier punto de este cribado previo es un rechazo de esa transacción (VerifierPanicked), no un nodo caído. Luego viene el mínimo de comisión: en la chain 20 el pool decodifica el límite de gas que declara una prueba de llamada o de invocación y rechaza una comisión inferior aBUNDLE_BASE + gas_price × limit + byte_price × ⌈bytes/1024⌉a los precios de la punta, más la comisión de celda de una invocación, comoFeeTooLow(Comisiones). Es la regla propia del bloque, de modo que el pool exige exactamente lo mismo que un bloque. - La mitad cara, fuera del bucle de consenso. La transacción se encola, con 64 de profundidad como máximo, para uno de los cuatro workers de verificación. Un worker ejecuta el resto de
Ledger::validateen una tarea bloqueante: el digest del bundle, el límite de gas que declara la prueba del bundle (en la chain 20 exactamente 20,479; de lo contrario,BundleGasLimit), la prueba del bundle y, desde la chain 17, su prueba de autorización contra el binding de la transacción, y, para una llamada, su propia prueba y su mínimo de comisión. Valida contra una instantánea de la punta que se clona de forma perezosa, como mucho una vez por cambio de punta y solo mientras algo espera. Una transacción difundida por gossip que encuentra la cola llena se descarta, sin verificarse ni reenviarse, en lugar de bloquear el bucle que vota; la siguiente ronda de gossip del par la vuelve a traer. - Re-comprobación e ingreso al pool.
insert_verifiedejecuta de nuevo el paso 4 contra la punta sobre la que la transacción entra realmente en el pool, porque la punta pudo moverse mientras se verificaba la prueba; luego la admite y, si procede del gossip, la reenvía.
Los pasos 1 a 4 son Mempool::precheck; el paso 5 es el único que verifica un STARK. Como ese coste se paga en un worker, una avalancha de pruebas inválidas ocupa cuatro hilos y nunca el bucle de consenso. Transacciones y acciones enumera el orden de admisión propio del libro mayor, que los pasos 4 y 5 ejecutan entre los dos.
Dos salvaguardas y una caché para el gossip
Antes de la admisión, dos mecanismos se aplican solo a las transacciones que llegan de pares; los envíos por RPC los omiten, porque ese puerto es del propio operador y ya está acotado por el límite del cuerpo de la solicitud. La única vía RPC que se mide es el faucet, más abajo.
- Un medidor de bytes por par. Desde v0.6.8, cada trama de gossip se carga al presupuesto de bytes de quien la reenvía antes incluso de decodificarla: una ráfaga de ocho y una recarga de dos tramas del tamaño máximo de transmisión por segundo; superado ese límite, la trama se descarta sin decodificar (auditoría v6, GOSSIP-1). El id de la transacción, que calcula el hash de toda la prueba, solo se calcula dentro de la asignación de quien la reenvía.
- Un medidor de transacciones por par. Un token bucket por cada par que reenvía, ráfaga de 16, con recarga de 4 por segundo (
PEER_TX_BURST,PEER_TX_PER_SEC). Mide al par que reenvió la transacción, no a su autor, a quien una cadena blindada no puede nombrar. - Una caché de veredictos. Una transacción que este nodo ya ha rechazado por un motivo que es función únicamente de sus bytes, como una prueba inválida, una firma inválida o una parte de tamaño excesivo, se vuelve a rechazar desde una caché FIFO de 8.192 entradas (
REFUSED_CACHE_ENTRIES) en lugar de verificarse por segunda vez. Un veredicto que dice algo sobre el estado de este nodo, un ancla desconocida o un nullifier gastado, nunca se almacena en caché: de lo contrario, un nodo un bloque atrasado se envenenaría contra transacciones que están a punto de ser válidas. La lista es una lista de permitidos, así que un error nuevo es estado hasta que alguien decida lo contrario. Estas entradas muestran dónde pasa la línea:BadPauseSignature(the pause signature does not verify) no es permanente desde v0.6.8 (auditoría v6, BRG-18). Comprueba la firma con la clave de pausa vigente de la cadena, que las reglas de puente v2 pueden rotar, así que es estado: un nodo un bloque atrasado respecto de una rotación rechazaría, de lo contrario, elPauseMintsdel nuevo titular hasta reiniciarse. Un índice de cofirma PQ fuera de rango salió de la caché por la misma razón. Hasta v0.6.7, en las cadenas 18 y 19, ambos se almacenaban en caché como permanentes. Una pausa inválida repetida cuesta ahora una verificación Dilithium2 por entrega, acotada por el medidor por par.PrunedFormOutsideSyncdeliberadamente no es permanente. La forma marcador de un paquete sellado produce un hash igual al id de la propia transacción en bruto, y la caché usa ese id como clave, de modo que almacenar el rechazo permitiría a cualquier par de gossip censurar la transacción honesta en toda la red.- El límite de gas declarado de una prueba son sus propios bytes frente a una constante del génesis, por lo que
BundleGasLimityAuthGasLimitsí son permanentes.FeeTooLowno lo es: en la cadena 20 el mínimo se mueve con los precios. Las tres políticas de cabecera del paso 4 tampoco se almacenan nunca en caché: cada una es el conocimiento de esta compilación, que una compilación posterior puede cambiar. - En una cadena con lista de permitidos del faucet (cadena 20),
FaucetRecipientNotAllowedsí es permanente: compara el destinatario de la propia emisión con una constante del génesis.FaucetBudgetExhaustedno lo es: es estado, y la misma emisión se admite de nuevo en la siguiente época. - El
BadSignaturede una acción de token tampoco es permanente, a diferencia del de una acción de staking: la autoridad de emisión de un token es estado, unSetAuthoritypuede cederla a otra clave, y los mismos bytes pueden rechazarse antes de que esa rotación se confirme y aceptarse después. Los rechazos de token a nivel de bytes (un nombre o símbolo inválido, demasiados decimales, un importe cero, el índice de token 0) se almacenan en caché; también un campo de quema que la acción no puede establecer, una emisión cuyo compromiso no se abre a su importe y un depósito de puente cuyo blinding no es el derivado.
El límite del faucet
rand_mint no cobra comisión y cuesta una transacción en el pool por llamada, y en la cadena 20 el faucet está activo con un puente que tiene valor detrás. El génesis limita lo que puede hacer: una emisión paga solo a las 16 wallets de la lista de permitidos y como máximo 10.000 RAND por época de 1.000 bloques (Staking), y solo una de las 18 claves de la lista staking.faucet_minters del génesis puede firmar una (MinterNotAllowed, nunca almacenada en caché: regla del ledger desde la cadena 16, política del nodo en la cadena 15, donde la lista eran los validadores del génesis). Además, cada nodo mide su propio faucet: ocho emisiones seguidas, con recarga de una por segundo (FAUCET_MINT_BURST, FAUCET_MINT_PER_SEC). Superado ese límite, la llamada responde faucet is rate limited on this node (8 mints back to back, refilling at 1/s); try again shortly. El límite es por proceso del nodo, no por llamante, porque el puerto RPC no tiene identidad de par que medir y el recurso protegido es el pool de este nodo. Junto con la exención de capacidad descrita arriba, una avalancha en el faucet no puede llenar el pool antes de un PauseMints.
Gossipsub se ejecuta con validación a nivel de aplicación, de modo que una transacción solo se reenvía a otros pares una vez que se ha verificado aquí. Un par que inunda con transacciones inválidas es limitado por el medidor, atendido desde la caché y no cuesta nada en el bucle de consenso.
Construcción de un bloque
Cuando un validador lidera una vista, pide candidatas al pool. candidates_within conserva solo las transacciones que todavía se aplican al ledger sobre el que construye, volviendo a ejecutar la mitad barata de la admisión: el ancla sigue en la ventana, el time sigue en ella, los nullifiers aún no se han gastado, los compromisos aún no están presentes. También descarta una acción de token cuyo mint_nonce o índice de registro la cadena ya ha superado, y una pausa o un listado con un nonce obsoleto. Desde v0.6.8 vuelve a fijar el precio de cada call e invoke con los precios vigentes en la punta sobre la que construye, a partir del límite de gas recordado en la admisión, y deja fuera aquella cuya comisión está ahora por debajo de su mínimo; sigue en el pool y se ofrece de nuevo cuando los precios bajan (auditoría v6, CH-9). En la cadena 20 ordena después el resto primero la gobernanza, luego por la comisión sobre el mínimo actual, por KiB de transacción, luego por comisión, de mayor a menor, y los empates se resuelven por hash, de modo que todo proponente honesto que construya sobre el mismo pool elige el mismo bloque, y llena hasta el presupuesto de 2.000 transacciones y el límite de bloque de la cadena (MAX_BLOCK_TXS; max_block_bytes, 4 MiB por defecto y 20 MiB en la cadena 20). Los bytes son lo que le falta a un bloque, por eso el excedente se toma por KiB. Una cadena sin sección gas y un nodo sin política de gas omiten esa clave y ordenan por gobernanza, comisión y hash, como hacía la cadena 15. La gobernanza primero es la otra mitad de la garantía de la pausa: un PauseMints no paga comisión por diseño, ya que debe funcionar desde una wallet sin RAND, de modo que un orden puro por comisión colocaría el freno de emergencia detrás de toda transacción que paga.
Se aplica una regla más mientras haya un call o un invoke listo (auditoría v6, POOL-2; política del nodo en todas las cadenas, no una regla de validez). Solo estos pagan el precio por byte, y una transferencia que paga un poco más que su base fija los supera por KiB, de modo que un flujo de transferencias podría mantener un call listo fuera de todos los bloques. Por eso las transacciones de comisión fija ocupan como máximo tres cuartas partes de los bytes del bloque en una primera pasada; una vez colocados los calls, el espacio que queda se llena con las transferencias retenidas, en su orden. No se pierde espacio de bloque, y si no hay ningún call listo nada cambia (Fees).
Una transacción demasiado grande para el espacio restante se omite, no termina el bloque: una más pequeña detrás de ella sigue viajando. Una transferencia de la cadena 18 pesa unos 2,85 MB, una prueba de paquete de 1,49 MB y una prueba de autorización de 1,36 MB, así que su bloque de 20 MiB tiene sitio para unas siete y un bloque por defecto de 4 MiB para una. En la cadena 15, con unos 1,43 MB por transferencia, los mismos 20 MiB admitían catorce. Bajo carga, la comisión es lo que decide cuáles (Fees).
Poda
Tras cada commit, prune retira del pool exactamente lo que candidates_within habría omitido: las transacciones cuya nota fue gastada por otra persona, cuyo compromiso ya existe, cuyo nonce o índice de registro la cadena ha superado, o cuyo ancla o time ha salido de la ventana (1.024 bloques en la cadena 20, 256 hasta la cadena 19). Un paquete que pierde una carrera de doble gasto desaparece por tanto por sí solo, sin producir nunca un error para nadie. La aceptación por rand_sendTransaction no es confirmación: consulta rand_getTransaction hasta que devuelva un bloque y, si la ventana del ancla pasa sin uno, reconstruye el paquete sobre un ancla nueva y envíalo de nuevo.
Observación
rand_status informa de mempool_size, verify_queue (transacciones en espera de un worker) y refused_cache (entradas de la caché de veredictos). Un verify_queue que se mantiene en 64 significa que las pruebas llegan más rápido de lo que cuatro workers las verifican, unos 16 ms cada una con una clave en caliente; un mempool_size que crece mientras los bloques siguen llenos significa que la comisión, no el orden de llegada, decide la inclusión.
Lo que aún no está
- Sin sustitución. Los precios del gas y de los bytes de la chain 20 mueven el piso de una llamada en cada bloque, hasta sus techos; el piso de una transferencia es fijo. No existe la sustitución de una transacción en el pool por otra de comisión más alta sobre las mismas notes, que la regla de conflicto rechaza. Una llamada que queda fuera de precio espera en el pool, sin ser ofrecida, hasta que los precios bajen o su ventana de ancla expire. En v0.6.7 (chains 18 y 19) una llamada en el pool conservaba el piso con el que fue admitida, seguía siendo ofrecida tras una subida y fallaba en la aplicación de prueba del proponente como
FeeTooLow. El documento de espacio de bloque del fullnode señala el mempool ordenado por comisión como el paso previo a un bloque más grande, y la agregación a nivel de bloque como lo que eleva el techo por bloque. - Sin expiración por antigüedad. Una transacción en el pool solo sale por inclusión, por expulsión al alcanzar el límite de bytes, o por las ventanas de ancla y de tiempo: 1.024 bloques en la chain 20, unos 23 minutos a los 1,34 s por bloque que el explorador reportó allí. Hasta la chain 19 las ventanas eran de 256 bloques, unos cinco minutos.
- Sin RPC de mempool. No existe ningún método que liste las transacciones pendientes;
mempool_sizeenrand_statusy el recuento, los bytes y la antigüedad máxima enrand_getMempoolInfoson las únicas vistas, y el explorador indexa solo los bloques confirmados.