本文详细介绍了 Solana Anchor 框架中的 [derive(Accounts)] 宏,解释了 Solana 并行交易处理机制及其账户访问控制的重要性,并深入探讨了 Account、UncheckedAccount、Signer 和 Program 四种账户类型的使用场景和实现细节。
Anchor中的#[derive(Accounts)]
:不同类型的账户
#[derive(Accounts)]
在Solana Anchor中是一个类似属性的宏,用于包含所有在执行期间将被函数访问的账户的引用。
在Solana中,交易将要访问的每个账户必须提前指定
Solana之所以如此快速的一个原因是它以并行的方式执行交易。也就是说,如果Alice和Bob都想进行一笔交易,Solana将尝试同时处理他们的交易。然而,如果他们的交易因访问同一存储而发生冲突,就会出现问题。例如,假设Alice和Bob都在尝试写入同一账户。显然,他们的交易不能并行执行。
为了让Solana知道Alice和Bob的交易不能并行处理,Alice和Bob都必须提前指定所有他们的交易将更新的账户。
由于Alice和Bob都指定了一个(存储)账户,Solana运行时可以推断出两个交易之间存在冲突。必须选择一个(推测是支付了更高优先级费用的那个),另一个最终将失败。
这就是为什么每个函数都有自己单独的#[derive(Accounts)]
结构体。结构体中的每个字段都是程序在执行期间打算(但并不要求)访问的账户。
一些以太坊开发者可能会注意到这一要求与EIP 2930访问列表交易的相似性。
账户的类型向Anchor信号你打算如何与该账户交互。
最常用的账户类型:Account、Unchecked Account、System Program和Signer
在我们初始化存储的代码中,我们看到了三种不同的“类型”账户:
Account
Signer
Program
这里是代码片段:
当我们读取账户余额时,我们看到了第四种类型:
UncheckedAccount
这里是我们使用的代码:
我们用绿色框标出的每个项目都是通过文件顶部的anchor_lang::prelude::*;
引入的。
Account
、UncheckedAccount
、Signer
和Program
的目的是在继续之前对传入的账户进行某种检查,并且还提供与这些账户交互的函数。
我们将在接下来的部分进一步解释这四种类型。
Account
Account
类型会检查被加载的账户的所有者是否确实被程序拥有。如果所有者不匹配,则不会加载。这作为一种重要的安全措施,以防止意外读取程序未创建的数据。
在以下示例中,我们创建了一个密钥对账户,并尝试将其传递给foo
。因为该账户不属于程序,所以交易失败。
Rust:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
##[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
// 我们不对账户SomeAccount做任何操作
Ok(())
}
}
##[derive(Accounts)]
pub struct Foo<'info> {
some_account: Account<'info, SomeAccount>,
}
##[account]
pub struct SomeAccount {}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor
.getProvider()
.connection.requestAirdrop(
publicKey,
amount * anchor.web3.LAMPORTS_PER_SOL
);
await confirmTransaction(airdropTx);
}
async function confirmTransaction(tx) {
const latestBlockHash = await anchor
.getProvider()
.connection.getLatestBlockhash();
await anchor
.getProvider()
.connection.confirmTransaction({
blockhash: latestBlockHash.blockhash,
lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
signature: tx,
});
}
// 配置客户端以使用本地区块链
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("账户拥有者错误", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 10);
await program.methods
.foo()
.accounts({someAccount: newKeypair
.publicKey}).rpc();
});
});
这是执行测试后的输出:
如果我们向Account
添加一个init
宏,那么它将尝试将所有权从系统程序转移到该程序。然而,上面的代码没有init
宏。
有关Account
类型的更多信息可以在文档中找到:https://docs.rs/anchor-lang/latest/anchor_lang/accounts/account/struct.Account.html
UncheckedAccount或AccountInfo
UncheckedAccount
是AccountInfo
的别名。它不检查所有权,因此必须小心,因为它将接受任意账户。
这是使用UncheckedAccount
读取一个不属于它的账户数据的示例。
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
##[program]
pub mod account_types {
use super::*;
pub fn foo(ctx: Context<Foo>) -> Result<()> {
let data = &ctx.accounts.some_account.try_borrow_data()?;
msg!("{:?}", data);
Ok(())
}
}
##[derive(Accounts)]
pub struct Foo<'info> {
/// 检查:我们只是打印数据
some_account: AccountInfo<'info>,
}
这是我们的Typescript代码。请注意,我们直接调用系统程序来创建密钥对账户,以便我们可以分配16字节的数据。
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
const wallet = anchor.workspace.AccountTypes.provider.wallet;
// 配置客户端以使用本地区块链
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("使用账户信息加载账户", async () => {
// 创建一个不属于程序的账户
const newKeypair = anchor.web3.Keypair.generate();
const tx = new anchor.web3.Transaction().add(
anchor.web3.SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: newKeypair.publicKey,
space: 16,
lamports: await anchor
.getProvider()
.connection
.getMinimumBalanceForRentExemption(32),
programId: program.programId,
})
);
await anchor.web3.sendAndConfirmTransaction(
anchor.getProvider().connection,
tx,
[wallet.payer, newKeypair]
);
// 读取账户中的数据
await program.methods
.foo()
.accounts({ someAccount: newKeypair.publicKey })
.rpc();
});
});
程序运行后,我们可以看到它打印了账户中的数据,该数据包含16个零字节:
在槽14298中执行的交易:
签名:64fv6NqYB4tji9UfLpH8PgFDY1QV4vbMovrnnpw3271vStg7J5g1z1bm9YbE8Lobzozkc6y2YzLdgMjGdftCGKqv
状态:成功
日志消息:
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs调用[1]
程序日志:指令:Foo
程序日志:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs消耗5334的200000计算单位
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs成功
当我们传入任意地址时,我们需要使用这种账户类型,但在使用数据时要非常小心,因为黑客可能能够在账户中构造恶意数据,然后将其传递给Solana程序。
Signer
该类型将检查Signer
账户是否签署了交易;它检查签名是否与账户的公钥匹配。
由于签名者也是一个账户,你可以读取签名者的余额或存储在账户中的数据(如果有的话),尽管它的主要目的是验证签名。
Rust示例:
use anchor_lang::prelude::*;
declare_id!("ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs");
##[program]
pub mod account_types {
use super::*;
pub fn hello(ctx: Context<Hello>) -> Result<()> {
let lamports = ctx.accounts.signer.lamports();
let address = &ctx.accounts
.signer
.signer_key().unwrap();
msg!(
"你好 {:?} 你有 {} lamports",
address,
lamports
);
Ok(())
}
}
##[derive(Accounts)]
pub struct Hello<'info> {
pub signer: Signer<'info>,
}
Typescript:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { AccountTypes } from "../target/types/account_types";
describe("account_types", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.AccountTypes as Program<AccountTypes>;
it("账户拥有者错误", async () => {
await program.methods.hello().rpc();
});
});
这里是程序的输出:
在槽11184中执行的交易:
签名:4xipobKHHp7a3N4durXN4YPGUesDAJNg7wsatBemdJAm7U1dXYG3gveLwnuY39iCTEZvaj6nnAViVJwDS8124uJJ
状态:成功
日志消息:
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs调用[1]
程序日志:指令:Hello
程序日志:你好5jmigjgt77kAfKsHri3MHpMMFPo6UuiAMF19VdDfrrTj你有499999994602666000 lamports
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs消耗13096的200000计算单位
程序ETnqC8mvPRyUVXyXoph22EQ1GS5sTs1zndkn5eGMYWfs成功
Program
这个应该是不言自明的。它向Anchor信号该账户是一个可执行账户,即一个程序,你可以向其发出跨程序调用。我们一直在使用的就是系统程序,稍后我们将使用我们自己的程序。
转载:https://learnblockchain.cn/article/11419
版权属于:区块链中文技术社区 / 转载原创者
本文链接:https://bcskill.com/index.php/archives/2358.html
相关技术文章仅限于相关区块链底层技术研究,禁止用于非法用途,后果自负!本站严格遵守一切相关法律政策!