本文介绍了Solana区块链中内置的多调用(multicall)功能,以及如何使用Anchor框架在Solana上进行批量交易。文章还详细解释了Solana交易大小限制,并展示了如何使用Rust和TypeScript代码实现原子性批量交易。
在以太坊中,如果我们想要原子地批量处理多个交易,我们使用多重调用模式。如果其中一个失败,其余的也会失败。
Solana 在运行时内置了这一功能,因此我们不需要实现多重调用。在下面的示例中,我们在一次交易中初始化一个账户并写入它——无需使用 init_if_needed
。
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Is initialized!", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
const initTx = await program.methods.initialize().accounts({pda: pda}).transaction();
// 对于 u32,我们不需要使用大数
const setTx = await program.methods.set(5).accounts({pda: pda}).transaction();
let transaction = new anchor.web3.Transaction();
transaction.add(initTx);
transaction.add(setTx);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
const pdaAcc = await program.account.pda.fetch(pda);
console.log(pdaAcc.value); // 输出 5
});
});
以下是对应的 Rust 代码:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
##[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
Ok(())
}
}
##[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
##[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
##[account]
pub struct PDA {
pub value: u32,
}
关于上面代码的一些评论:
u32
值或更小值传递给 Rust 时,我们不需要使用 Javascript 大数。await program.methods.initialize().accounts({pda: pda}).rpc()
,而是使用 await program.methods.initialize().accounts({pda: pda}).transaction()
来创建一个交易。Solana 交易的总大小不能超过 1232 字节。
这意味着你无法批量处理“无限”数量的交易并支付更多 gas,就像在以太坊中那样。
让我们修改 Rust 中的 set
函数以始终失败。这将帮助我们看到,如果其中一个后续批处理交易失败,initialize
交易会被回滚。
以下 Rust 程序在调用 set
时总是返回错误:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
##[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
return err!(Error::AlwaysFails);
}
}
##[error_code]
pub enum Error {
#[msg(always fails)]
AlwaysFails,
}
##[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
##[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
##[account]
pub struct PDA {
pub value: u32,
}
以下 Typescript 代码发送初始化和设置的批处理交易:
import * as anchor from "@coral-xyz/anchor";
import { Program, SystemProgram } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
// 配置客户端以使用本地集群。
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Set the number to 5, initializing if necessary", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
// 输出 pda 的地址
console.log(pda.toBase58());
let transaction = new anchor.web3.Transaction();
transaction.add(await program.methods.initialize().accounts({pda: pda}).transaction());
transaction.add(await program.methods.set(5).accounts({pda: pda}).transaction());
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
});
});
当我们运行测试,然后查询本地验证器以获取 pda 账户时,我们发现它不存在。即使初始化交易在前,随后的设置交易的回滚导致整个交易被取消,因此没有账户被初始化。
你可以使用前端代码模拟 init_if_needed
的行为,同时拥有一个单独的 initialize
函数。然而,从用户的角度来看,他们在第一次使用账户时无需发出多个交易。
要确定一个账户是否需要初始化,我们检查它是否有零 lamports 或被系统程序拥有。以下是如何在 Typescript 中实现此功能:
import * as anchor from "@coral-xyz/anchor";
import { Program, SystemProgram } from "@coral-xyz/anchor";
import { Batch } from "../target/types/batch";
describe("batch", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.Batch as Program<Batch>;
it("Set the number to 5, initializing if necessary", async () => {
const wallet = anchor.workspace.Batch.provider.wallet.payer;
const [pda, _bump] = anchor.web3.PublicKey.findProgramAddressSync([], program.programId);
let accountInfo = await anchor.getProvider().connection.getAccountInfo(pda);
let transaction = new anchor.web3.Transaction();
if (accountInfo == null || accountInfo.lamports == 0 || accountInfo.owner == anchor.web3.SystemProgram.programId) {
console.log("需要初始化");
const initTx = await program.methods.initialize().accounts({pda: pda}).transaction();
transaction.add(initTx);
}
else {
console.log("无需初始化");
}
// 我们无论如何要设置数字
const setTx = await program.methods.set(5).accounts({pda: pda}).transaction();
transaction.add(setTx);
await anchor.web3.sendAndConfirmTransaction(anchor.getProvider().connection, transaction, [wallet]);
const pdaAcc = await program.account.pda.fetch(pda);
console.log(pdaAcc.value);
});
});
我们还需要修改我们的 Rust 代码,以 不 在 set
操作上强制失败。
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE");
##[program]
pub mod batch {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn set(ctx: Context<Set>, new_val: u32) -> Result<()> {
ctx.accounts.pda.value = new_val;
Ok(()) // 移除了错误
}
}
##[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init, payer = signer, space = size_of::<PDA>() + 8, seeds = [], bump)]
pub pda: Account<'info, PDA>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
##[derive(Accounts)]
pub struct Set<'info> {
#[account(mut)]
pub pda: Account<'info, PDA>,
}
##[account]
pub struct PDA {
pub value: u32,
}
如果我们对同一个本地验证器实例运行两次测试,我们将获得以下输出:
第一次测试运行:
第二次测试运行:
如果你创建一个新的 Solana 程序并 run anchor deploy
(或 anchor test
),你将在日志中看到多个对 BFPLoaderUpgradeable
的交易:
Transaction executed in slot 65695:
Signature: 62Zu3NPyjjaEoH4XSc7kULtuoszLPctM1PTrLiC7A3CiaGJEzYscQ5c9SKbN3UUoqctyrdzW2upDXnSC4VnMjyfZ
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 3cD19SGmdfd991NjcGHpYcnjhZ3FYqEWnHMJALQ95X5fvwHVhB3Cw9PwqSDwziiCMQHcZ8iuxXqg3UDJmp7gJHd3
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 5apuTjqCMKGdyYGRZ9sCLDapPCKqjyJMyqWMC24EsW4pLzHhM3YUgnf5Q2sqXSLVTxjKaSgZ3fcCkZrAah32uzh2
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: HJ8XaErydn8ojxaEknZsg43pGA9mC8TBqV4zwSrZgXFvi5UqgZjNU65TQKqb6DyEZFtHecytt1k7U4N9Vw52rur
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 3uY9beX23VdRXeEqUSP4cpAuTevdcjHDZ8K3pwKVpw51mwX1jLGQ7LYB7d68dWSe571TeAoxq33eoUU7c8gTDgic
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65695:
Signature: 666r5LcQaH1ZcZWhrHFUFEqjHXEE1QUyh27HFRkWsDQihM7FYtyz3v4eJgVkQwhJuMDSYHJZHDRrSsNVbCFrEkV9
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 2QmPZFkDN9WsKiNjHFdaNLuaYbQFXtN8yRgHTDC3Ce2z28483LNVyuE1AnwgsRisiKeiKe5Wu9WTbkTbAwmodPTC
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: EsTiuCn6PGA158Xi43XwGtYf2tDJTbgxRJehHS9AQ9AcW4qraxWuNPzdD7Wk4yeL65oaaa1G8WMqkjYbJcGzhv1V
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 3PZSv4dnggW52C3FL9E1JPvwueBp7E342o9aM29mH2CnfGsGLDBRJcN64EQeJEkc57hgGyZsiz8J1fSV1Qquz8zx
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65696:
Signature: 4ynMY9ioELf4xxtBpHeM1q2fuWM5usa1w8dXQhLhjstR8U6LmpYHTJs7Gc82XkVyMXywPrsbu3EDCAcpoFj7qwkJ
Status: Ok
Log Messages:
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program BFPLoaderUpgradeab1e11111111111111111111111 success
Transaction executed in slot 65698:
Signature: 5rs38HHbWF2ZrsgDCux1X9FRvkrhTdrEimdhidd2EYbaeezAmy9Tv5AFULgsarPtJCft8uZmsvhpYKwHGxnLf2sG
Status: Ok
Log Messages:
Program 11111111111111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 success
Program BFPLoaderUpgradeab1e11111111111111111111111 invoke [1]
Program 11111111111111111111111111111111 invoke [2]
Program 11111111111111111111111111111111 success
Deployed program Ao9LdZtHdMAzrFUEfRNbKEb5H4nXvpRZC69kxeAGbTPE
Program BFPLoaderUpgradeab1e11111111111111111111111 success
这里,Anchor 将部署字节码的过程分解为多个交易,因为一次性部署整个字节码将无法在单个交易中适应。通过将日志重定向到文件,我们可以计算发生了多少次交易:
solana logs > logs.txt
## 在另一个 shell 中运行 `anchor deploy`
grep "Transaction executed" logs.txt | wc -l
这将大致匹配在 anchor test
或 anchor deploy
命令后暂时显示的情况:
有关交易如何批处理的确切过程描述,可以参见 Solana 文档:如何部署 Solana 程序。
交易列表是单独的交易,而不是批量交易。如果是批量交易,它将超过 1232 字节限制。
查看我们的 Solana 开发课程 以获取更多 Solana 教程。
最初发表于 2024 年 3 月 10 日
- 原文链接: rareskills.io/post/solan...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!