Одноразовый номер учетной записи — единственный элемент в структуре транзакции, который предотвращает атаку воспроизведения (как объясняется в этом очень хорошем ответе ). По сути, это принудительно упорядочивает транзакции по счету.
Обычно это не вызывает проблем, когда транзакции отправляются через интерфейс web3 на узел Ethereum (например, Geth, Parity), который затем подписывает их. Последовательность выполняется внутри узла и, таким образом, прозрачна для конечного пользователя.
Это не тот случай, когда транзакции создаются и подписываются пользовательским приложением (т. е. не Geth или Parity) перед отправкой на узел (например, через web3.eth.sendRawTransaction).
Создание необработанной транзакции требует получения одноразового номера учетной записи, что можно сделать с помощью web3.eth.getTransactionCount, как описано в этом вопросе . Это прекрасно работает, когда параллелизм не задействован.
Однако рассмотрим конечного пользователя, который должен подписывать несколько транзакций параллельно. Последовательность транзакций становится обязанностью пользовательского приложения.
«web3.eth.getTransactionCount» возвращает только количество подтвержденных транзакций (см. этот вопрос ). В результате, когда транзакции подписываются параллельно, если мы не будем разумно манипулировать счетчиком, только одна из них будет успешной.
Мой вопрос: каковы некоторые хорошие шаблоны для обработки этой ситуации?
Это позволяет параллельные транзакции. Однако это вносит дополнительную сложность в пользовательское приложение. Кроме того, это не слишком масштабируемо: подумайте, нужны ли десятки или сотни одновременных транзакций. Кроме того, учитывая, что иерархический детерминированный кошелек не является родным для Ethereum (я знаю, что существует LightWallet ), это не похоже на простое решение.
Это создает цикл, который проверяет результат отправки транзакции. Если возвращаемый результат указывает на повторяющийся одноразовый номер (например, Geth вернет «транзакцию замещения по заниженной цене»), повторите попытку с новым одноразовым номером.
Это решение не является переносимым: разные клиенты возвращают разные строки для одной и той же ошибки. На практике я, кажется, заметил, что Geth иногда даже не возвращает указанную выше строку, а молча отбрасывает другие транзакции (не подтверждено, так как я не нашел надежного способа воспроизвести). В результате обнаружение не работает надежно.
Каждая конструкция транзакции включает в себя «запрос» одноразового номера от глобального «менеджера одноразовых номеров». Одноэлементный «менеджер одноразовых номеров» может иметь некоторую умную логику для раздачи номеров одноразовых номеров по возрастанию. Это использует поведение, обсуждаемое здесь .
Это не только вводит синглтон (возможно, анти-шаблон), но и кажется непростым сделать это правильно: если транзакция с низким одноразовым номером каким-то образом терпит неудачу (например, даже не отправляется в сеть), остальные транзакций застревает на неопределенный срок. В общем, это плохое решение, которое создает больше сложностей и проблем, чем попытки их решить.
Это не совсем решение. Наиболее очевидная проблема заключается в том, что дополнительная зависимость не кажется оправданной, если она касается только поля nonce.
Кроме того, если пользовательское приложение уже управляет закрытыми ключами, это создает дополнительную сложность: закрытые ключи должны совместно управляться (или копироваться) узлом Geth или Parity. Опять же, сложность (и потенциальная уязвимость) не кажется оправданной, если она касается только одноразового номера.
Не по теме. Мне кажется, что учетная запись nonce — не лучший дизайн в Ethereum. Это заставляет пользовательские приложения либо обрабатывать детали протокола низкого уровня, либо вводить зависимость от узла (Geth/Parity). В любом случае это добавляет непропорционально громоздкости пользовательским приложениям.
Я еще не дошел до того момента, когда я могу протестировать любую из этих вещей, но я чувствую, что Singleton Nonce Manager будет подходящим вариантом с несколькими улучшениями:
Детали реализации потребуют близкого знакомства с параллельным программированием, блокировками, мьютексами, асинхронностью или чем-то еще. По сути, вышеизложенное является предположением, поскольку я еще не работал напрямую с вызовами RPC и не могу его протестировать, но я думаю, что это основа пути, по которому я бы пошел. Я вижу, что структура очереди будет предлагать внутренний API для перечисления ожидающих транзакций, обработанных и поддержки других операций пользовательского интерфейса в качестве бонуса.
getTransactionCount также может включать ожидающие транзакции, что является лучшим способом определения одноразового номера для последующей необработанной транзакции. Это решение простое, но маловероятное без собственных проблем. Но для запуска нескольких транзакций в тесной последовательности это, кажется, работает, как и ожидалось.
nonce: web3.toHex(web3.eth.getTransactionCount(fromAddress, "pending")),
Все зависит от вашего контекста. Если вы являетесь конечным пользователем, пытающимся самостоятельно выполнить несколько транзакций, вы можете просто где-нибудь сохранить одноразовый номер и увеличивать его для каждой параллельной транзакции. Очевидно, вы хотите избежать создания промежутков в одноразовом номере, но пока вы на 100% уверены, что сможете покрыть средства при выполнении транзакций, временное промежутки допустимы. Худшее, что может случиться, это то, что одноразовый номер воспроизводится дважды, и в этом случае вы создали недопустимые транзакции.
Если вы действуете от имени пользователя и пытаетесь выполнять массовые транзакции, вы можете просто использовать атомарную операцию базы данных для хранения одноразового номера. Это может работать в гипотетическом пакетном кошельке, пытающемся отправить несколько транзакций одновременно. Вы, вероятно, захотите сделать заявление об отказе от ответственности, что пользователь должен избегать использования ключа при взаимодействии с кошельком, чтобы предотвратить рассинхронизацию внутреннего счетчика.
Линьмао Сонг
Страж
Злой Джордан
Линьмао Сонг
рунекс