43

From Design Rationale:

One weakness of the account paradigm is that in order to prevent replay attacks, every transaction must have a "nonce", such that the account keeps track of the nonces used and only accepts a transaction if its nonce is 1 after the last nonce used.


There have been some questions on this site about transactions nonces that are too low. What happens when a transaction nonce is too high?

4 Answers 4

106

Summary

  • Transactions with too low a nonce get immediately rejected.
  • Transactions with too high a nonce get placed in the transaction pool queue.
  • If transactions with nonces that fill the gap between the last valid nonce and the too high nonce are sent and the nonce sequence is complete, all the transactions in the sequence will get processed and mined.
  • When the geth instances are shut down and restarted, transactions in the transaction pool queue disappear.
  • The transaction pool queue will only hold a maximum of 64 transactions with the same From: address with nonces out of sequence.
  • The geth instance can be crashed by filling up the transaction pool queue with 64 transactions with the same From: address with nonces out of sequence by, for example:
    • Creating many accounts with a minimal amount of ethers (0.05 ETH in my tests)
    • Sending 64 transactions from each account with a large data payload
    • For the 4 Gb memory limit that I placed on my geth instance, 400 x 64 transactions with a payload of about 4,500 bytes would crash the geth instance (from my limited testing anyway).
    • These transactions with too high a nonce do NOT propagate to other nodes and crash other nodes on the Ethereum network.
  • The Ethereum World Computer cannot be brought down with transactions with too high a nonce. Good work, developers!


Details below.



What Happens When The Transaction Nonce Is Too Low?

I've created two fresh accounts on my private --dev network using

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}

I have started a mining geth instance in a separate window, so the first account (coinbase) is getting deposited with more funds as new blocks are being mined.

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

In my main window I have started a geth instance attaching to the mining instance.

geth --datadir ./data --dev attach

I send my first transaction from my first account to my second account

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

Here are the transaction details for the first transaction. A nonce of 0 has been automatically allocated for this transaction.

 > 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
 }

I send a second transaction from my first account to my second account, specifying a nonce of 0, and get the expected result of "Nonce too low".

> 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



What Happens When The Transaction Nonce Is Too High?

I now send a third transaction from my first account to my second account, specifying a nonce of 10000, and get a transaction hash back that the transaction has been sent to the transaction pool.

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

In the mining window, a message shows the transaction has been received. However the transaction is never mined.

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

I try to retrieve the transaction details for my third transactions. The blockHash and blockNumber remain null for ever.

> 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
}

I check the transaction pool status and I am assuming that the transaction with nonce 10000 is in the queue.

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

I try sending a fourth transaction with a nonce of 1. The transaction gets mined.

> 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
}

So I try sending a fifth transaction with a nonce of 3 (there is now a gap as the last valid nonce is 1). The transaction goes into the transaction pool queue and does not get mined.

> 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
}

I send a sixth transaction with a nonce of 2 (this fills in the gap between the last valid nonce of 1 and the queued transaction with a nonce of 3).

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

Both the transactions with the nonces of 2 and 3 now get mined.

> 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
}

I check the transaction pool status and the transaction with nonce 10000 is still in the queue, and will remain forever.

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

I shut down my mining geth instance and my attached geth instance, and restart both of them. I now check the transaction pool status and the transaction with nonce 10000 has disappeared.

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



Transaction Pool Source Code

Looking at core/tx_pool.go (#48-50), there is a maximum queue size of 64 transactions for transactions with out-of-order nonce sequence per sending address.

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

And core/tx_pool.go (#436-456) shows the code that removes transactions if the queue is too full:

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 With Too High A Nonce

  • For my testing I created one mining geth instance with a peer-to-peer connected non-mining geth instance, on a private dev network.
  • I limited the mining geth instance with 4Gb by switching off my swap file (running sudo swapoff -a on Linux) and running other memory hogging programs.
  • I created a Perl script to iteratively (1 .. 20,000) create new accounts on my mining geth instance and transfer 0.05 ETH from my coinbase into the new accounts.
  • I created another Perl script to iteratively (1 .. 20,000) unlock each new account in the mining geth instance and send 64 transaction with the nonce set too high and with a data payload of ~ 4,500 bytes.
  • The transactions with too high a nonce ended up filling the transaction pool queue of the mining geth instance and crashing geth at about the 400th iteration. My computer also shut down.
  • The non-mining geth instance did not receive any of the transaction with too high a nonce.
  • To confirm that the transaction with too high a nonce does not propagate from node to node, I also manually created transaction with too high a nonce on the non-mining geth instance and only the transaction pool queue of the non-mining geth instance filled up.
10
  • 13
    Whatever intelligence /question-answering drug you're taking, I want some. Epic work. 🌟
    – tayvano
    Apr 9, 2016 at 8:05
  • 2
    10 more users like Bokky, and we are set for Shanghai. <3
    – q9f
    Apr 9, 2016 at 10:19
  • 3
    Added information to the Summary and added new Crash Testing Geth section. The Ethereum World Computer is safe from attack with transactions with too high a nonce - from my limited testing anyway. Apr 10, 2016 at 9:00
  • 3
    To add to these comments.. this answer is superb. Thanks a lot for taking the time to answer this. Nov 23, 2016 at 13:26
  • 3
    If I've to pick one best answer on StackExchange, I'll pick this one. Aug 29, 2018 at 4:38
1

What happens when a transaction nonce is too high?

"too high" nonce transaction could be added into tx_pool and pass first stage validation, but it will be failed in second stage validation during state transition execution.
It strictly followed Design Rationale:"only accepts a transaction if its nonce is 1 after the last nonce used."

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
}
1
  • 4
    I'm quite amazed only +1 nonce is accepted. That, in my opinion, adds unnecessary complexity and sync-requirements between 2 instances using the same account. Also with offline nodes or light nodes, they need to know the network state to sign a transaction.. I get why nonce is needed but why is it so strict that it only allows +1?
    – EralpB
    Jul 5, 2017 at 9:21
1

if you're using hardhat + metamask, and seeing this after restarting the node, try resetting your account on metamask: Settings > Advanced > Reset Account.

-3

In Blockchain.info exchange, making use of shapeshift transfers between two crypto's eg ethereum to bitcoin, this message appears if your first transaction status is still pending.

It is to safeguard against double transactions. Wait untill the first transaction is completed before attempting to shift more.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.