文章详细介绍了在Solana区块链上如何使用不同的签名者初始化账户并进行更新操作,通过Rust代码和客户端代码示例,展示了如何实现账户管理和权限控制。
在我们迄今为止的 Solana 教程中,我们只初始化并写入一个账户。
在实际操作中,这非常有限制。举个例子,如果用户 Alice 正在将积分转移给 Bob,Alice 必须能够写入由用户 Bob 初始化的账户。
在本教程中,我们将演示如何使用一个钱包初始化一个账户,并使用另一个钱包更新它。
我们用于初始化账户的 Rust 代码没有改变:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("61As9Y8pREgvFZzps6rpFai8UkageeHT6kW1dnGRiefb");
#[program]
pub mod other_write {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = signer,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub signer: Signer<'info>,
pub system_program: Program<'info, System>,
}
#[account]
pub struct MyStorage {
x: u64,
}
然而,客户端代码中有一个重要的变化:– 为了测试,我们创建了一个名为 newKeypair
的新钱包。与 Anchor 默认提供的一个不同。– 我们向新钱包空投 1 SOL,这样它可以支付交易费用。– 请注意注释 // THIS MUST BE EXPLICITLY SPECIFIED
。我们正在将该钱包的 publicKey 传递给 Signer
字段。当我们使用内置 Anchor 的默认签名者时,Anchor 在后台为我们传递这个。但是,当我们使用不同的钱包时,我们需要显式提供这个。– 我们通过 .signers([newKeypair])
配置设置签名者为 newKeypair
。
在这个代码片段之后我们将解释为何我们看起来是在两次指定签名者:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherWrite } from "../target/types/other_write";
// 此函数向地址空投 SOL
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount);
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,
});
}
describe("other_write", () => {
anchor.setProvider(anchor.AnchorProvider.env());
const program = anchor.workspace.OtherWrite as Program<OtherWrite>;
it("Is initialized!", async () => {
const newKeypair = anchor.web3.Keypair.generate();
await airdropSol(newKeypair.publicKey, 1e9); // 1 SOL
let seeds = [];
const [myStorage, _bump] = anchor.web3.PublicKey.findProgramAddressSync(seeds, program.programId);
await program.methods.initialize().accounts({
myStorage: myStorage,
signer: newKeypair.publicKey // ** THIS MUST BE EXPLICITLY SPECIFIED **
}).signers([newKeypair]).rpc();
});
});
signer
这个键并不一定要叫 signer
。
练习:在 Rust 代码中,将 payer = signer
改为 payer = fren
,并将 pub signer: Signer<'info>
改为 pub fren: Signer<'info>
,然后在测试中将 signer: newKeypair.publicKey
改为 fren: newKeypair.publicKey
。初始化应该会成功,测试应该通过。
乍一看,似乎我们在两次指定签名者是多余的,但让我们仔细看看:
在红色框中,我们看到 fren
字段被指定为一个 Signer 账户。Signer
类型意味着 Anchor 将查看交易的签名,并确保签名与此处传递的地址匹配。
稍后我们将看到如何使用它来验证签名者是否被授权进行某些交易。
Anchor 一直在幕后执行这一操作,但由于我们传入了一个与 Anchor 默认使用的签名者不同的 Signer
,所以我们必须明确指定 Signer
是哪个账户。
unknown signer
错误发生在交易的签名者与传递给 Signer
的 public key 不匹配时。
假设我们修改了测试,去除 .signers([newKeypair])
规范。Anchor 将使用默认签名者,而默认签名者不会匹配我们的 newKeypair
钱包的 publicKey
:
我们将得到以下错误:
类似地,如果我们没有明确传入 publicKey
,Anchor 将默默地使用默认签名者:
我们将得到以下 错误:未知的签名者
:
有些误导性地,Anchor 并不是因为没有指定签名者而说签名者是未知的。Anchor 能够推断出如果没有指定签名者,则将使用默认签名者。如果我们同时去除 .signers([newKeypair])
代码和 fren: newKeypair.publicKey
代码,那么 Anchor 将为验证公共密钥进行签名,并且验证签名者的签名是否与公共密钥匹配。
以下代码将导致成功初始化,因为 Signer
的公共密钥和签署交易的账户都是 Anchor 默认签名者。
await program.methods.initialize().accounts({
myStorage: myStorage
}).rpc();
下面我们展示一个带有初始化账户和写入功能的 Anchor 程序。
这将让人熟悉我们的 Solana 计数器程序教程,但注意底部标记的 // THIS FIELD MUST BE INCLUDED
注释中的小更改:
use anchor_lang::prelude::*;
use std::mem::size_of;
declare_id!("61As9Y8pREgvFZzps6rpFai8UkageeHT6kW1dnGRiefb");
#[program]
pub mod other_write {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Ok(())
}
pub fn update_value(ctx: Context<UpdateValue>, new_value: u64) -> Result<()> {
ctx.accounts.my_storage.x = new_value;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(init,
payer = fren,
space=size_of::<MyStorage>() + 8,
seeds = [],
bump)]
pub my_storage: Account<'info, MyStorage>,
#[account(mut)]
pub fren: Signer<'info>, // 此处传递了一个公共密钥
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateValue<'info> {
#[account(mut, seeds = [], bump)]
pub my_storage: Account<'info, MyStorage>,
// THIS FIELD MUST BE INCLUDED
#[account(mut)]
pub fren: Signer<'info>,
}
#[account]
pub struct MyStorage {
x: u64,
}
以下客户端代码将为 Alice 和 Bob 创建钱包,并向他们分别空投 1 SOL。Alice 将初始化 MyStorage
账户,Bob 将写入它:
import * as anchor from "@coral-xyz/anchor";
import { Program } from "@coral-xyz/anchor";
import { OtherWrite } from "../target/types/other_write";
// 此函数向地址空投 SOL
async function airdropSol(publicKey, amount) {
let airdropTx = await anchor.getProvider().connection.requestAirdrop(publicKey, amount);
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,
});
}
describe("other_write", () => {
// 配置客户端以使用本地集群。
anchor.setProvider(anchor.AnchorProvider.env());
const program = anch...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!