Одноразовые номера устойчивых транзакций
Проблема
Чтобы предотвратить повтор, транзакции Solana содержат поле одноразового номера, заполненное «последним» значением хэша блока. Транзакция, содержащая слишком старый блокхеш (на момент написания этой статьи ~2 минуты), отклоняется сетью как недействительная. К сожалению, в некоторых случаях использования, таких как кастодиальные услуги, требуется больше времени для создания подписи для транзакции. Необходим механизм для включения этих потенциально автономных участников сети.
Требования
- Подпись транзакции должна покрывать значение одноразового номера.
- Одноразовый номер не должен использоваться повторно, даже в случае раскрытия ключа подписи.
Решение на основе контракта
Здесь мы описываем основанное на контракте решение проблемы, при котором клиент может «спрятать» одноразовое значение для будущего использования в поле recent_blockhash
транзакции. Этот подход похож на атомарную инструкцию Compare and Swap, реализованную некоторыми процессорными ISA.
При использовании устойчивого одноразового номера клиент должен сначала запросить его значение из данных учетной записи. Теперь транзакция строится обычным образом, но со следующими дополнительными требованиями:
- Значение устойчивого одноразового номера используется в поле
recent_blockhash
- Инструкция «AdvanceNonceAccount» выдается первой в транзакции.
Механика контрактов
TODO: svgbob это в блок-схему
Start
Create Account
state = Uninitialized
NonceInstruction
if state == Uninitialized
if account.balance < rent_exempt
error InsufficientFunds
state = Initialized
elif state != Initialized
error BadState
if sysvar.recent_blockhashes.is_empty()
error EmptyRecentBlockhashes
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
stored_hash = sysvar.recent_blockhashes[0]
success
WithdrawInstruction(to, lamports)
if state == Uninitialized
if !signers.contains(owner)
error MissingRequiredSignatures
elif state == Initialized
if !sysvar.recent_blockhashes.contains(stored_nonce)
error NotReady
if lamports != account.balance && lamports + rent_exempt > account.balance
error InsufficientFunds
account.balance -= lamports
to.balance += lamports
success
Клиент, желающий использовать эту функцию, начинает с создания одноразовой учетной записи в системной программе. Эта учетная запись будет находиться в состоянии «Неинициализированная» без сохраненного хэша и, следовательно, непригодной для использования.
Чтобы инициализировать вновь созданную учетную запись, необходимо выполнить инструкцию InitializeNonceAccount. Эта инструкция принимает один параметр, Pubkey
[полномочия] учетной записи (../offline-signing/durable-nonce.md#nonce-authority). Одноразовые учетные записи должны быть освобождены от аренды, чтобы соответствовать требованиям к сохранению данных этой функции, и поэтому требуют внесения достаточного количества ламповых портов, прежде чем их можно будет инициализировать. После успешной инициализации последний блок-хэш кластера сохраняется вместе с указанным одноразовым ключом Pubkey.
Инструкция AdvanceNonceAccount используется для управления сохраненным значением одноразового номера учетной записи. Он сохраняет самый последний хеш-блок кластера в данных состояния учетной записи, терпя неудачу, если он соответствует уже хранящемуся там значению. Эта проверка предотвращает повторное воспроизведение транзакций в одном и том же блоке.
Из-за требования rent-exempt учетных записей nonce для вывода средств со счета используется пользовательская инструкция по снятию средств.
Инструкция WithdrawNonceAccount
принимает один аргумент, указывает на вывод средств и обеспечивает освобождение от арендной платы, предотвращая падение баланса счета ниже минимума, освобожденного от арендной платы. Исключением из этой проверки является случай, когда окончательный баланс будет равен нулю лампортов, что делает учетную запись подходящей для удаления. Для этой детали закрытия учетной записи есть дополнительное требование, согласно которому сохраненное значение одноразового номера не должно совпадать с самым последним хэшем блока в соответствии с AdvanceNonceAccount
.
[nonce полномочия] учетной записи (../offline-signing/durable-nonce.md#nonce-authority) можно изменить с помощью инструкции AuthorizeNonceAccount
. Он принимает один параметр, Pubkey
нового органа. Выполнение этой инструкции предоставляет новым полномочиям полный контроль над счетом и его балансом.
AdvanceNonceAccount
,WithdrawNonceAccount
иAuthorizeNonceAccount
требуют текущих одноразовых полномочий для учетной записи, чтобы подписать транзакцию.
Поддержка во время выполнения
Одного контракта недостаточно для реализации этой функции. Чтобы применить существующий recent_blockhash
к транзакции и предотвратить кражу комиссий из-за неудачного воспроизведения транзакции, необходимы модификации во время выполнения.
Любая транзакция, не прошедшая обычную проверку check_hash_age
, будет проверена на наличие одноразового номера устойчивой транзакции. Об этом сигнализирует включение инструкции «AdvanceNonceAccount» в качестве первой инструкции в транзакции.
Если среда выполнения определяет, что используется одноразовый номер устойчивой транзакции, она предпримет следующие дополнительные действия для проверки транзакции:
- Загружается
NonceAccount
, указанный в инструкцииNonce
. NonceState
десериализуется из поля данныхNonceAccount
и подтверждается, что он находится в состоянииInitialized
.- Значение nonce, хранящееся в
NonceAccount
, проверяется на соответствие значению, указанному в полеrecent_blockhash
транзакции.
Если все три вышеуказанные проверки завершаются успешно, транзакция может продолжить проверку.
Поскольку с транзакций, завершившихся ошибкой «InstructionError», взимается комиссия, а изменения их состояния откатываются, существует возможность кражи комиссии, если отменяется инструкция «AdvanceNonceAccount». Злонамеренный валидатор может воспроизвести неудачную транзакцию до тех пор, пока сохраненный одноразовый номер не будет успешно расширен. Изменения во время выполнения предотвращают такое поведение. Когда длительная транзакция одноразового номера завершается с ошибкой InstructionError помимо инструкции AdvanceNonceAccount, учетная запись одноразового номера, как обычно, откатывается в состояние, предшествующее выполнению. Затем среда выполнения увеличивает свое значение одноразового номера, и расширенная учетная запись одноразового номера сохраняется, как если бы это было успешно.