* import zh translations
* Fix broken links
* fix whitespace
(cherry picked from commit a1df57a4ea
)
Co-authored-by: Justin Starry <justin@solana.com>
5.3 KiB
title
title |
---|
持久交易编号(Nonces) |
问题
为了防止重花,Solana 交易包含一个有“最近”块哈希值的非空值。 包含太久的(撰文时为 ~2分钟) 区块哈希交易被网络拒绝为无效。 很不幸,某些情况下(例如托管服务),需要更多时间来生成交易的签名。 需要一种机制来支持这些潜在的线下网络参与者。
需求
- 交易签名必须包括编号值
- 即使是在签名密钥披露的情况下,nonce 也不可以重复使用。
基于合约的解决办法
这里我们描述了一种基于合约的解决方法,其中客户端可以在最近一次交易的 recent_blockhash
字段中“存放”一个未来可以使用的 nonce 值。 这个方法类似于通过一些 CPU ISA 实现的比较和交换原子指令。
当使用后续的 nonce 时,客户端必须首先从账户数据查询它的值。 现在的交易是正常的,但需要满足以下附加要求:
- 后续的 nonce 值用于
recent_blockhash
字段 AdvanceNonceAccount
指令是交易中第一次发出的
合约机制
未完成工作: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
客户端想要使用该功能,首先需要在系统程序下创建一个 nonce 帐户。 此帐户将处于 Uninitialized
状态,且没有存储哈希,因此无法使用。
要初始化该新创建的帐户,必须发出 InitializeNonceAccount
指令。 该指令需要一个 Pubkey
参数,它位于账户 授权 中。 Nonce 帐户必须是 rent-exempt 状态,才能满足数据持续性功能的要求, 因此它要求初始化之前先存入足够的 lamports。 初始化成功后,集群最近的区块哈希与指定 nonce 授权的 Pubkey
将一同存储。
AdvanceNonceAccount
指令用于管理帐户存储的 nonce 值。 它在账户的状态数据中存储集群最新的区块哈希,如果与已存储的值相匹配,那么会提示失败。 这个检查可以防止在同一个区块内重新广播交易。
由于 nonce 帐户的 免租 要求,一个自定义提现指令用于将资产从帐户中移出。 WithdrawNonceAccount
指令需要一个单一参数,提示取款信号,强制免除租金,防止账户余额下降至低于免租金的最低值。 该检查的一个例外情况是,最终余额是零,从而让账户能够删除。 这个账户关闭详细信息还有一个额外要求,即存储的 nonce 值必须与集群最近的区块不匹配, 正如 AdvanceNonceAccount
。
账户的 nonce authority 可以通过 AuthorizeNonceAccount
说明进行更改。 它需要传入一个参数,新授权的 Pubkey
。 执行该指令将把完全的帐户及其余额控制权转移给新的授权。
AdvanceNonceAccount
,WithdrawNonceAccount
和AuthorizeNonceAccount
都需要当前 nonce authority 才能签署交易。
运行时(Runtime)支持
合约本身并不足以实现这个功能。 为了在交易上强制执行一个现有的recent_blockhash
,并防止通过失败的交易重放来窃取费用,runtime的修改是必要的。
任何未能通过通常的check_hash_age
验证的交易将被测试为持久交易Nonce。 这是由包括一个AdvanceNonceAccount
指令作为交易中的第一条指令发出的信号。
如果runtime确定使用了一个持久事务Nonce,它将采取以下额外的操作来验证事务:
- 加载
Nonce
指令中指定的NonceAccount
。 NonceAccount
的数据字段反序列化NonceState
,并确认其处于Initialized
状态。- 存储在
NonceAccount
中的nonce值与交易的recent_blockhash
字段中指定的nonce值进行匹配测试。
如果上述三项检查都成功,则允许交易继续验证。
由于以InstructionError
失败的交易会被收取费用,并且其状态的改变会被回滚,所以如果AdvanceNonceAccount
指令被回滚,则费用可能会被盗。 恶意验证者可以重放失败的交易,直到存储的nonce被成功推进。 Runtime的更改可以防止这种行为。 当一个持久的nonce事务失败时,除了AdvanceNonceAccount
指令外,还有一个InstructionError
,nonce账户会像往常一样被回滚到执行前的状态。 然后,runtime将其nonce值和高级nonce账户存储起来,就像已经成功了。