Что происходит, когда одноразовый номер транзакции слишком велик?

Из обоснования дизайна :

Одним из недостатков парадигмы учетной записи является то, что для предотвращения атак воспроизведения каждая транзакция должна иметь «одноразовый номер», чтобы учетная запись отслеживала используемые одноразовые номера и принимала транзакцию только в том случае, если ее одноразовый номер равен 1 после последнего используемого одноразового номера.


На этом сайте было несколько вопросов о слишком низком одноразовом числе транзакций. Что происходит, когда одноразовый номер транзакции слишком велик?

Ответы (4)

Резюме

  • Транзакции со слишком низким одноразовым номером немедленно отклоняются.
  • Транзакции со слишком большим одноразовым номером помещаются в очередь пула транзакций.
  • Если отправляются транзакции с одноразовыми номерами, которые заполняют промежуток между последним допустимым одноразовым номером и слишком высоким одноразовым номером, и последовательность одноразовых номеров завершена, все транзакции в последовательности будут обработаны и добыты.
  • Когда экземпляры geth выключаются и перезапускаются, транзакции в очереди пула транзакций исчезают.
  • Очередь пула транзакций будет содержать не более 64 транзакций с одним и тем же From:адресом с неупорядоченными одноразовыми номерами.
  • Экземпляр geth может выйти из строя, заполнив очередь пула транзакций 64 транзакциями с одним и тем же From:адресом с неупорядоченными одноразовыми номерами, например:
    • Создание множества аккаунтов с минимальным количеством эфиров (0,05 ETH в моих тестах)
    • Отправка 64 транзакций с каждой учетной записи с большой полезной нагрузкой данных
    • При ограничении памяти в 4 ГБ, которое я установил для своего экземпляра geth, транзакции 400 x 64 с полезной нагрузкой около 4500 байт привели бы к сбою экземпляра geth (во всяком случае, из моего ограниченного тестирования).
    • Эти транзакции со слишком высоким одноразовым номером НЕ распространяются на другие узлы и приводят к сбою других узлов в сети Ethereum.
  • Мировой компьютер Ethereum не может быть отключен транзакциями со слишком высоким одноразовым номером. Молодцы, разработчики!


Подробности ниже.



Что происходит, когда одноразовый номер транзакции слишком мал?

Я создал две новые учетные записи в своей частной сети --dev, используя

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}

Я запустил экземпляр geth для майнинга в отдельном окне, поэтому на первую учетную запись (coinbase) пополняется больше средств по мере добычи новых блоков.

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

В моем главном окне я запустил экземпляр geth, подключенный к экземпляру майнинга.

geth --datadir ./data --dev attach

Я отправляю свою первую транзакцию с моей первой учетной записи на мою вторую учетную запись

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

Вот детали транзакции для первой транзакции. Для этой транзакции автоматически выделяется одноразовый номер 0.

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

Я отправляю вторую транзакцию из моей первой учетной записи в мою вторую учетную запись, указав одноразовый номер 0, и получаю ожидаемый результат «Слишком низкий одноразовый номер».

> 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



Что происходит, когда одноразовый номер транзакции слишком высок?

Теперь я отправляю третью транзакцию из моей первой учетной записи во вторую учетную запись, указав одноразовый номер 10000, и получаю обратно хэш транзакции, подтверждающий, что транзакция была отправлена ​​в пул транзакций.

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

В окне майнинга появится сообщение о том, что транзакция получена. Однако транзакция никогда не добывается.

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

Я пытаюсь получить детали транзакции для моих третьих транзакций. blockHash и blockNumber всегда остаются нулевыми.

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

Я проверяю состояние пула транзакций и предполагаю, что транзакция с одноразовым номером 10000 находится в очереди.

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

Я пытаюсь отправить четвертую транзакцию с одноразовым номером 1. Транзакция заминирована.

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

Поэтому я пытаюсь отправить пятую транзакцию с одноразовым номером 3 (теперь есть пробел, поскольку последний действительный одноразовый номер равен 1). Транзакция попадает в очередь пула транзакций и не добывается.

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

Я отправляю шестую транзакцию с одноразовым номером 2 (это заполняет пробел между последним допустимым одноразовым номером 1 и транзакцией в очереди с одноразовым номером 3).

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

Обе транзакции с одноразовыми номерами 2 и 3 теперь добываются.

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

Я проверяю состояние пула транзакций, и транзакция с одноразовым номером 10000 все еще находится в очереди и останется навсегда.

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

Я выключил свой экземпляр geth для майнинга и подключенный экземпляр geth и перезапустил их оба. Теперь я проверяю статус пула транзакций, и транзакция с одноразовым номером 10000 исчезла.

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



Исходный код пула транзакций

Глядя на core/tx_pool.go (#48-50) , максимальный размер очереди составляет 64 транзакции для транзакций с неупорядоченной последовательностью одноразовых номеров на адрес отправителя.

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

А core/tx_pool.go (#436-456) показывает код, удаляющий транзакции, если очередь переполнена:

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



Краш-тестирование Geth со слишком высоким одноразовым номером

  • Для тестирования я создал один экземпляр geth для майнинга с одноранговым подключением экземпляра geth без майнинга в частной сети разработки.
  • Я ограничил экземпляр geth для майнинга 4 ГБ, отключив файл подкачки (работающий sudo swapoff -aв Linux) и запустив другие программы, занимающие много памяти.
  • Я создал Perl-скрипт для итеративного (от 1 до 20 000) создания новых учетных записей на моем инстансе geth для майнинга и перевода 0,05 ETH из моей coinbase на новые учетные записи.
  • Я создал еще один Perl-скрипт, чтобы итеративно (1 .. 20 000) разблокировать каждую новую учетную запись в экземпляре майнинга geth и отправить 64 транзакции со слишком большим одноразовым номером и полезной нагрузкой данных ~ 4500 байт.
  • Транзакции со слишком большим одноразовым номером закончились тем, что заполнили очередь пула транзакций майнингового экземпляра geth и завершились сбоем geth примерно на 400-й итерации. Мой компьютер тоже выключился.
  • Экземпляр geth, не занимающийся майнингом, не получил ни одной транзакции со слишком высоким одноразовым номером.
  • Чтобы подтвердить, что транзакция со слишком высоким одноразовым номером не распространяется от узла к узлу, я также вручную создал транзакцию со слишком высоким одноразовым номером на экземпляре geth, не занимающемся майнингом, и заполнилась только очередь пула транзакций экземпляра geth, не занимающегося майнингом. .
Какой бы наркотик интеллекта/вопросов-ответов вы ни принимали, я хочу немного. Эпическая работа. 🌟
Я сделал это из любопытства и нашел потенциальный вектор атаки в сети Ethereum, а может и нет. Хотя слишком сложно это проверить. Спасибо.
Еще 10 пользователей любят Bokky, и мы отправимся в Шанхай . <3
Добавлена ​​информация в сводку и добавлен новый раздел Crash Testing Geth. Компьютер Ethereum World защищен от атак с транзакциями со слишком высоким одноразовым номером — во всяком случае, из моего ограниченного тестирования.
Чтобы добавить к этим комментариям... этот ответ превосходен. Большое спасибо, что нашли время ответить на этот вопрос.
Вы делали что-нибудь перед ` sudo swapoff -a `, чтобы установить 4 ГБ? В чем преимущество swapoff? @BokkyPooBah
Моя физическая память была 4Gb. Кроме того, у меня был файл подкачки размером 4 ГБ, поэтому общая эффективная память составила 8 ГБ. swapoff -aотключает файл подкачки, так что моя эффективная память составляет всего 4 ГБ.
Почему узлы не допускают использование одноразовых номеров со слишком высоким значением? Было бы неплохо иметь возможность быстро отправлять транзакции, не проверяя, была ли каждая из них успешно добыта. В настоящее время, если вы это сделаете, вы рискуете получить временной пробел.
@TheOfficiousBokkyPooBah Что произойдет, если два разных приложения dApp, полностью изолированные, отправят транзакции для одной и той же учетной записи пользователя? Видя, что они не общаются по поводу выбора одноразового номера, это кажется огромной проблемой? Или я что-то упускаю? Я думал, что одноразовые номера должны быть непрерывными (за исключением дубликатов с более высоким газом) для конкретной учетной записи. Если пользователь инициирует транзакции в двух разных dApp или два dApp отправляются от имени пользователя, как вы можете получить непрерывный поток nonce?
Если мне нужно выбрать один лучший ответ на StackExchange, я выберу этот.

если вы используете каску + метамаску и видите это после перезапуска узла, попробуйте сбросить свою учетную запись в метамаске: «Настройки»> «Дополнительно»> «Сбросить учетную запись».

Что происходит, когда одноразовый номер транзакции слишком велик?

Транзакция с "слишком высоким" одноразовым значением может быть добавлена ​​в tx_pool и пройти проверку на первом этапе, но она не пройдет проверку на втором этапе во время выполнения перехода состояния.
Он строго следовал Обоснованию дизайна: «принимает транзакцию только в том случае, если ее одноразовый номер равен 1 после последнего использованного одноразового номера».

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 одноразовый номер. Это, на мой взгляд, добавляет ненужную сложность и требования к синхронизации между двумя экземплярами, использующими одну и ту же учетную запись. Также с автономными узлами или легкими узлами им необходимо знать состояние сети, чтобы подписать транзакцию. Я понимаю, почему необходим одноразовый номер, но почему он настолько строг, что разрешает только +1?

В обмене Blockchain.info, использующем переводы с изменением формы между двумя криптовалютами, например, эфириум в биткойн, это сообщение появляется, если статус вашей первой транзакции все еще находится на рассмотрении.

Это защита от двойных транзакций. Подождите, пока первая транзакция не будет завершена, прежде чем пытаться сдвинуть больше.