背景
离线交易
常规Solana交易都是典型短生命周期(大约1分钟),依赖recent_blockhash。如果想提前离线生成交易,等待较长时间再发送,则会过期,交易失败。
持久交易 nonce 是一种绕过交易的典型短生命周期的机制 recent_blockhash
顺序执行
持久交易 nonce可以让交易按照顺序执行
创建Nonce账户
账户地址可以通过create-nonce-account创建nonce账户,nonce账户用于存放下一个nonce值,nonce账户必须是免租的,所以需要持有最低的余额。
solana-keygen new -o nonce-keypair.json
solana create-nonce-account nonce-keypair.json 1
查询nonce
solana nonce nonce-keypair.json
输出
8GRipryfxcsxN8mAGjy8zbFo9ezaUsh47TsPzmZbuytU
提高存储的 Nonce
solana new-nonce nonce-keypair.json
显示nonce账户
solana nonce-account nonce-keypair.json
输出
balance: 0.5 SOL
minimum balance required: 0.00136416 SOL
nonce: DZar6t2EaCFQTbUP4DHKwZ1wT8gCPW2aRfkVWhydkBvS
从nonce账户提取资金
solana withdraw-from-nonce-account nonce-keypair.json ~/.config/solana/id.json 0.5
通过提取全部余额来关闭 nonce 账户
创建 nonce 账户后重新分配权限
solana authorize-nonce-account nonce-keypair.json nonce-authority.json
CLI 实测
这里我们演示了 Alice 使用持久随机数向 Bob 支付 1 个 SOL。对于所有支持持久随机数的子命令,该过程都是相同的
创建
首先,我们需要一些 Alice、Alice 的随机数和 Bob 的账户
solana-keygen new -o alice.json
solana-keygen new -o nonce.json
solana-keygen new -o bob.json
为Alice领取测试代币
Alice 需要一些资金来创建一个 nonce 账户并发送给 Bob。空投一些 SOL
solana airdrop -k alice.json 1
1 SOL
创建 Alice 的 nonce
现在 Alice 需要一个 nonce 账户。创建一个
这里没有使用 单独的nonce 权限alice.json,因此对 nonce 账户拥有完全的权限
solana create-nonce-account -k alice.json nonce.json 0.1
3KPZr96BTsL3hqera9up82KAU462Gz31xjqJ6eHUAjF935Yf8i1kmfEbo6SVbNaACKE5z6gySrNjVRvmS8DcPuwV
第一次向Bob转账
Alice 尝试向 Bob 付款,但签名时间过长。指定的区块哈希过期,交易失败
$ solana transfer -k alice.json --blockhash expiredDTaxfagttWjQweib42b6ZHADSx94Tw8gHx11 bob.json 0.01
[2025-03-06T18:48:28.462911000Z ERROR solana_cli::cli] Io(Custom { kind: Other, error: "Transaction \"33gQQaoPc9jWePMvDAeyJpcnSPiGUAdtVg8zREWv4GiKjkcGNufgpcbFyRKRrA25NkgjZySEeKue5rawyeH5TzsV\" failed: None" })
Error: Io(Custom { kind: Other, error: "Transaction \"33gQQaoPc9jWePMvDAeyJpcnSPiGUAdtVg8zREWv4GiKjkcGNufgpcbFyRKRrA25NkgjZySEeKue5rawyeH5TzsV\" failed: None" })
Nonce 来救援
Alice 重试交易,这次指定她的 nonce 账户和存储在那里的区块哈希
记住,在这个例子中,alice.json是nonce 权限
solana nonce-account nonce.json
balance: 0.1 SOL
minimum balance required: 0.00136416 SOL
nonce: F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7
$ solana transfer -k alice.json --blockhash F7vmkY3DTaxfagttWjQweib42b6ZHADSx94Tw8gHx3W7 --nonce nonce.json bob.json 0.01
HR1368UKHVZyenmH7yVz5sBAijV6XAPeWbEiXEGVYQorRMcoijeNAbzZqEZiH8cDB8tk65ckqeegFjK8dHwNFgQ
成功
交易成功!Bob 从 Alice 处收到 0.01 SOL,Alice 存储的 nonce 值增加到新值
solana balance -k bob.json
0.01 SOL
solana nonce-account nonce.json
balance: 0.1 SOL
minimum balance required: 0.00136416 SOL
nonce: 6bjroqDcZgTv6Vavhqf81oBHTv3aMnX19UTB51YhAZnN
测试代码
const {
Connection,
Keypair,
PublicKey,
LAMPORTS_PER_SOL,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
NonceAccount
} = require('@solana/web3.js');
async function main() {
// 连接到 Solana 网络
const connection = new Connection('https://api.devnet.solana.com', 'confirmed');
// 创建一个新的 Keypair 作为 Nonce 账户的所有者
const owner = Keypair.generate();
// 创建一个新的 Keypair 作为 Nonce 账户
const nonceAccount = Keypair.generate();
// 计算 Nonce 账户所需的租金
const nonceRent = await connection.getMinimumBalanceForRentExemption(NonceAccount.span);
// 创建一个创建 Nonce 账户的交易
const createNonceAccountTransaction = new Transaction().add(
SystemProgram.createNonceAccount({
fromPubkey: owner.publicKey,
noncePubkey: nonceAccount.publicKey,
lamports: nonceRent
})
);
// 发送并确认创建 Nonce 账户的交易
await sendAndConfirmTransaction(connection, createNonceAccountTransaction, [owner, nonceAccount]);
// 获取 Nonce 账户的状态
const nonceAccountState = await connection.getNonce(nonceAccount.publicKey);
let nonce = nonceAccountState.nonce;
// 离线签署 10 笔交易
for (let i = 0; i < 10; i++) {
// 创建一个新的交易
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: owner.publicKey,
toPubkey: new PublicKey('your_recipient_address'),
lamports: LAMPORTS_PER_SOL * 0.01 // 示例金额
})
);
// 设置 Nonce 信息
transaction.recentBlockhash = nonce;
transaction.feePayer = owner.publicKey;
// 签署交易
transaction.sign(owner);
// 在这里你可以将交易序列化并保存,以便离线发送
const serializedTransaction = transaction.serialize();
console.log(`第 ${i + 1} 笔交易的 Nonce:`, nonce);
// 推进 Nonce
const advanceNonceTransaction = new Transaction().add(
SystemProgram.advanceNonceAccount({
noncePubkey: nonceAccount.publicKey,
authorizedPubkey: owner.publicKey
})
);
await sendAndConfirmTransaction(connection, advanceNonceTransaction, [owner]);
// 获取新的 Nonce
const newNonceAccountState = await connection.getNonce(nonceAccount.publicKey);
nonce = newNonceAccountState.nonce;
}
}
main().catch(console.error);
总结
- 如果想离线长期保存交易或者期望交易按顺序执行,可以使用nonce账户
- 实现过程,查询对应的nonce账户的nonce值,代替recent_blockhash
- CLI中每次交易后nonce账户的nonce值会自动推进更新,对于代码调用,需要发起SystemProgram.advanceNonceAccount交易主动推进
- nonce值无法本地离线计算推进,每次都需要链上发起更新
https://docs.anza.xyz/cli/examples/durable-nonce
版权属于:区块链中文技术社区 / 转载原创者
本文链接:https://bcskill.com/index.php/archives/2273.html
相关技术文章仅限于相关区块链底层技术研究,禁止用于非法用途,后果自负!本站严格遵守一切相关法律政策!