¿Qué sucede cuando un nonce de transacción es demasiado alto?

De la justificación del diseño :

Una debilidad del paradigma de la cuenta es que para evitar ataques de repetición, cada transacción debe tener un "nonce", de modo que la cuenta realiza un seguimiento de los nonces utilizados y solo acepta una transacción si su nonce es 1 después del último nonce utilizado.


Ha habido algunas preguntas en este sitio sobre los nonces de transacciones que son demasiado bajos. ¿Qué sucede cuando un nonce de transacción es demasiado alto?

Respuestas (4)

Resumen

  • Las transacciones con un nonce demasiado bajo se rechazan inmediatamente.
  • Las transacciones con un nonce demasiado alto se colocan en la cola del grupo de transacciones.
  • Si se envían transacciones con nonces que llenan el espacio entre el último nonce válido y el nonce demasiado alto y la secuencia de nonce está completa, todas las transacciones en la secuencia se procesarán y extraerán.
  • Cuando las instancias de geth se apagan y se reinician, las transacciones en la cola del grupo de transacciones desaparecen.
  • La cola del grupo de transacciones solo contendrá un máximo de 64 transacciones con la misma From:dirección con nonces fuera de secuencia.
  • La instancia de geth se puede bloquear llenando la cola del grupo de transacciones con 64 transacciones con la misma From:dirección con nonces fuera de secuencia, por ejemplo:
    • Crear muchas cuentas con una cantidad mínima de éteres (0.05 ETH en mis pruebas)
    • Envío de 64 transacciones desde cada cuenta con una gran carga de datos
    • Para el límite de memoria de 4 Gb que puse en mi instancia de geth, 400 x 64 transacciones con una carga útil de alrededor de 4500 bytes bloquearían la instancia de geth (de todos modos, de mi prueba limitada).
    • Estas transacciones con un nonce demasiado alto NO se propagan a otros nodos y bloquean otros nodos en la red Ethereum.
  • El Ethereum World Computer no se puede derribar con transacciones con un nonce demasiado alto. ¡Buen trabajo, desarrolladores!


Detalles abajo.



¿Qué sucede cuando el Nonce de la transacción es demasiado bajo?

Creé dos cuentas nuevas en mi red privada --dev usando

geth -datadir ./data --dev account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {c18f2996c11ba48c7e14233710e5a8580c4fb9ee}

geth -datadir ./data --dev account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase: 
Repeat Passphrase: 
Address: {e1fb110faa8850b4c6be5bdb3b7940d1ede87dfb}

Comencé una instancia de geth de minería en una ventana separada, por lo que la primera cuenta (coinbase) se deposita con más fondos a medida que se extraen nuevos bloques.

geth --datadir ./data --dev --mine --minerthreads 1 console

En mi ventana principal, comencé una instancia de geth que se adjunta a la instancia de minería.

geth --datadir ./data --dev attach

Envío mi primera transacción desde mi primera cuenta a mi segunda cuenta

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether")})
Unlock account c18f2996c11ba48c7e14233710e5a8580c4fb9ee
Passphrase: 
"0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47"

Aquí están los detalles de la transacción para la primera transacción. Se ha asignado automáticamente un nonce de 0 para esta transacción.

 > eth.getTransaction("0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47")
 {
   blockHash: "0x519ddf8c3d1a094933d2975bb7c9cdf3680c9d66b880ba22b26627f70d90bb54",
   blockNumber: 88,
   from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
   gas: 90000,
   gasPrice: 20000000000,
   hash: "0xd17510a4c9880155b0237cac58423e05b61ad3f2d5ee90f72d3db2b7d4ea2d47",
   input: "0x",
   nonce: 0,
   to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
   transactionIndex: 0,
   value: 1000000000000000000
 }

Envío una segunda transacción desde mi primera cuenta a mi segunda cuenta, especificando un nonce de 0, y obtengo el resultado esperado de "Nonce demasiado bajo".

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:0})
Nonce too low
    at InvalidResponse (<anonymous>:-81662:-106)
    at send (<anonymous>:-156322:-106)
    at sendTransaction (<anonymous>:-133322:-106)
    at <anonymous>:1:1



¿Qué sucede cuando el Nonce de la transacción es demasiado alto?

Ahora envío una tercera transacción desde mi primera cuenta a mi segunda cuenta, especificando un nonce de 10000, y obtengo un hash de transacción que indica que la transacción se envió al grupo de transacciones.

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:10000})
"0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d"

En la ventana de minería, un mensaje muestra que se ha recibido la transacción. Sin embargo, la transacción nunca se extrae.

I0409 15:25:07.699859   10726 worker.go:569] commit new work on block 95 with 0 txs & 0 uncles. Took 289.587µs
I0409 15:25:08.493883   10726 xeth.go:1028] Tx(0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d) to: 0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb
> I0409 15:26:13.472919   10726 worker.go:348] 🔨  Mined block (#95 / 7fe1ada0). Wait 5 blocks for confirmation
I0409 15:26:13.473634   10726 worker.go:569] commit new work on block 96 with 0 txs & 0 uncles. Took 630.605µs
I0409 15:26:13.473707   10726 worker.go:447] 🔨 🔗  Mined 5 blocks back: block #90
I0409 15:26:13.474252   10726 worker.go:569] commit new work on block 96 with 0 txs & 0 uncles. Took 447.451µs
I0409 15:26:18.921404   10726 worker.go:348] 🔨  Mined block (#96 / 760e117c). Wait 5 blocks for confirmation
I0409 15:26:18.922033   10726 worker.go:569] commit new work on block 97 with 0 txs & 0 uncles. Took 547.204µs
I0409 15:26:18.922096   10726 worker.go:447] 🔨 🔗  Mined 5 blocks back: block #91

Intento recuperar los detalles de la transacción para mis terceras transacciones. blockHash y blockNumber permanecen nulos para siempre.

> eth.getTransaction("0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d")
{
  blockHash: null,
  blockNumber: null,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0x5b09270d6bcd33297527a1f6b08fa1528deec01e82a100c7e62ee93fbdcd1f7d",
  input: "0x",
  nonce: 10000,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: null,
  value: 1000000000000000000
}

Verifico el estado del grupo de transacciones y asumo que la transacción con nonce 10000 está en la cola.

> txpool.status
{
  pending: 0,
  queued: 1
}

Intento enviar una cuarta transacción con un nonce de 1. La transacción se extrae.

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:1})
"0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8"
> eth.getTransaction("0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8")
{
  blockHash: "0xc125f5da96e36ac87728a35eae8ff8046bcc08c6242825daa4b6bb1e7b460a01",
  blockNumber: 101,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0x545af0a0276e154a8669921373de8904a330b829318a7c83f5bd9f9771e71ff8",
  input: "0x",
  nonce: 1,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: 0,
  value: 1000000000000000000
}

Así que intento enviar una quinta transacción con un nonce de 3 (ahora hay una brecha ya que el último nonce válido es 1). La transacción entra en la cola del grupo de transacciones y no se extrae.

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:3})
"0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461"
> eth.getTransaction("0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461")
{
  blockHash: null,
  blockNumber: null,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461",
  input: "0x",
  nonce: 3,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: null,
  value: 1000000000000000000
}

Envío una sexta transacción con un nonce de 2 (esto llena el espacio entre el último nonce válido de 1 y la transacción en cola con un nonce de 3).

> eth.sendTransaction({from: eth.accounts[0], to: eth.accounts[1], value: web3.toWei(1, "ether"), nonce:2})
"0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4"

Ambas transacciones con los nonces de 2 y 3 ahora se minan.

> eth.getTransaction("0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4")
{
  blockHash: "0xabcfea8140fdbe3d04bab05cb0232a8c73de4a6bc2307907ede9d45ad58d7107",
  blockNumber: 170,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0xea7a6350d6f7aa61a5f515452021de905917338d3b4d354e19fc53d8bd4982f4",
  input: "0x",
  nonce: 2,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: 0,
  value: 1000000000000000000
}
> eth.getTransaction("0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461")
{
  blockHash: "0xabcfea8140fdbe3d04bab05cb0232a8c73de4a6bc2307907ede9d45ad58d7107",
  blockNumber: 170,
  from: "0xc18f2996c11ba48c7e14233710e5a8580c4fb9ee",
  gas: 90000,
  gasPrice: 20000000000,
  hash: "0x895ec329c3a1d53acf7a429721025f2ff01d5558feee0595daa0fa9c0282d461",
  input: "0x",
  nonce: 3,
  to: "0xe1fb110faa8850b4c6be5bdb3b7940d1ede87dfb",
  transactionIndex: 1,
  value: 1000000000000000000
}

Verifico el estado del grupo de transacciones y la transacción con nonce 10000 todavía está en la cola y permanecerá para siempre.

> txpool.status
{
  pending: 0,
  queued: 1
}

Cierro mi instancia geth de minería y mi instancia geth adjunta, y reinicio ambas. Ahora verifico el estado del grupo de transacciones y la transacción con nonce 10000 ha desaparecido.

> txpool.status
{
  pending: 0,
  queued: 0
}



Código fuente del grupo de transacciones

Mirando core/tx_pool.go (#48-50) , hay un tamaño máximo de cola de 64 transacciones para transacciones con secuencia nonce fuera de orden por dirección de envío.

const (
    maxQueued = 64 // max limit of queued txs per address
)

Y core/tx_pool.go (#436-456) muestra el código que elimina las transacciones si la cola está demasiado llena:

for i, entry := range promote {
    // If we reached a gap in the nonces, enforce transaction limit and stop
    if entry.Nonce() > guessedNonce {
        if len(promote)-i > maxQueued {
            if glog.V(logger.Debug) {
                glog.Infof("Queued tx limit exceeded for %s. Tx %s removed\n", common.PP(address[:]), common.PP(entry.hash[:]))
            }
            for _, drop := range promote[i+maxQueued:] {
                delete(txs, drop.hash)
            }
        }
        break
    }
    // Otherwise promote the transaction and move the guess nonce if needed
    pool.addTx(entry.hash, address, entry.Transaction)
    delete(txs, entry.hash)

    if entry.Nonce() == guessedNonce {
        guessedNonce++
    }
}



Crash Testing Geth con un Nonce demasiado alto

  • Para mis pruebas, creé una instancia geth de minería con una instancia geth no minera conectada entre pares, en una red de desarrollo privada.
  • Limité la instancia de geth de minería con 4 Gb apagando mi archivo de intercambio (que se ejecuta sudo swapoff -aen Linux) y ejecutando otros programas de acaparamiento de memoria.
  • Creé un script de Perl para crear iterativamente (1 .. 20 000) nuevas cuentas en mi instancia de minería geth y transferir 0,05 ETH de mi base de monedas a las nuevas cuentas.
  • Creé otra secuencia de comandos de Perl para desbloquear iterativamente (1 .. 20 000) cada cuenta nueva en la instancia geth de minería y enviar 64 transacciones con el valor de nonce establecido demasiado alto y con una carga útil de datos de ~ 4500 bytes.
  • Las transacciones con un nonce demasiado alto terminaron llenando la cola del grupo de transacciones de la instancia de geth de minería y colapsando geth aproximadamente en la iteración 400. Mi computadora también se apagó.
  • La instancia geth no minera no recibió ninguna transacción con un nonce demasiado alto.
  • Para confirmar que la transacción con un nonce demasiado alto no se propaga de un nodo a otro, también creé manualmente una transacción con un nonce demasiado alto en la instancia geth no minera y solo se llenó la cola del grupo de transacciones de la instancia geth no minera .
Cualquiera que sea la droga de inteligencia / respuesta a preguntas que estés tomando, quiero un poco. Obra épica. 🌟
Hice esto por curiosidad y encontré un vector de ataque potencial en la red Ethereum, tal vez no. Sin embargo, es demasiado difícil probarlo. Gracias.
A 10 usuarios más les gusta Bokky, y estamos listos para Shanghái . <3
Se agregó información al Resumen y se agregó una nueva sección de Crash Testing Geth. El Ethereum World Computer está a salvo de ataques con transacciones con un nonce demasiado alto, de todos modos, según mis pruebas limitadas.
Para agregar a estos comentarios ... esta respuesta es excelente. Muchas gracias por tomarse el tiempo para responder esto.
¿Hiciste algo antes de `sudo swapoff -a` para establecer 4 GB? ¿Cuál fue la ventaja del intercambio? @BokkyPooBah
Mi memoria física era de 4Gb. Además de eso, tenía un archivo de intercambio de 4 Gb, por lo que la memoria efectiva total era de 8 Gb. swapoff -aapaga el archivo de intercambio, por lo que mi memoria efectiva es de solo 4 Gb.
¿Por qué los nodos no permiten nonces que son demasiado altos? Sería bueno poder enviar transacciones rápidamente sin tener que verificar si cada una se extrajo con éxito. Actualmente, si hace eso, corre el riesgo de tener una brecha de nonce.
@TheOfficiousBokkyPooBah ¿Qué sucede si dos dApps diferentes, completamente aisladas, envían transacciones para la misma cuenta de usuario? Al ver que no se están comunicando con respecto a la selección de nonce, ¿esto parece un gran problema? ¿O me estoy perdiendo algo? Pensé que los nonce tenían que ser contiguos (a excepción de los duplicados de mayor contenido de gas) para una cuenta en particular. Si un usuario inicia transacciones en dos dApps diferentes, o dos dApps se envían en nombre del usuario, ¿cómo demonios podría terminar con una transmisión nonce contigua?
Si tengo que elegir una mejor respuesta en StackExchange, elegiré esta.

si está usando hardhat + metamask y ve esto después de reiniciar el nodo, intente restablecer su cuenta en metamask: Configuración> Avanzado> Restablecer cuenta.

¿Qué sucede cuando un nonce de transacción es demasiado alto?

La transacción nonce "demasiado alta" podría agregarse a tx_pool y pasar la validación de la primera etapa, pero fallará en la validación de la segunda etapa durante la ejecución de la transición de estado.
Seguía estrictamente la razón de diseño: "solo acepta una transacción si su nonce es 1 después del último nonce utilizado".

go-ethereum/core/state_transition.go

func (self *StateTransition) preCheck() (err error) {
    msg := self.msg
    sender := self.from()

    // Make sure this transaction's nonce is correct
    if msg.CheckNonce() {
        if n := self.state.GetNonce(sender.Address()); n != msg.Nonce() {
            return NonceError(msg.Nonce(), n)
        }
    }

    // Pre-pay gas
    if err = self.buyGas(); err != nil {
        if IsGasLimitErr(err) {
            return err
        }
        return InvalidTxError(err)
    }

    return nil
}
Estoy bastante sorprendido de que solo se acepte +1 nonce. Eso, en mi opinión, agrega complejidad innecesaria y requisitos de sincronización entre 2 instancias que usan la misma cuenta. También con nodos fuera de línea o nodos ligeros, necesitan conocer el estado de la red para firmar una transacción. Entiendo por qué se necesita nonce, pero ¿por qué es tan estricto que solo permite +1?

En el intercambio Blockchain.info, al hacer uso de transferencias de cambio de forma entre dos criptos, por ejemplo, ethereum a bitcoin, este mensaje aparece si el estado de su primera transacción aún está pendiente.

Es para salvaguardar contra transacciones dobles. Espere hasta que se complete la primera transacción antes de intentar cambiar más.