使用不同签名者修改账户

  • RareSkills
  • 发布于 2024-03-17 17:44
  • 阅读 197

文章详细介绍了在Solana区块链上如何使用不同的签名者初始化账户并进行更新操作,通过Rust代码和客户端代码示例,展示了如何实现账户管理和权限控制。

展示 Anchor Signer:使用不同签名者修改账户的英雄图像

在我们迄今为止的 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。初始化应该会成功,测试应该通过。

为什么 Anchor 需要指定 Signer 和 publicKey?

乍一看,似乎我们在两次指定签名者是多余的,但让我们仔细看看:

公共密钥在此传递

在红色框中,我们看到 fren 字段被指定为一个 Signer 账户。Signer 类型意味着 Anchor 将查看交易的签名,并确保签名与此处传递的地址匹配。

稍后我们将看到如何使用它来验证签名者是否被授权进行某些交易。

Anchor 一直在幕后执行这一操作,但由于我们传入了一个与 Anchor 默认使用的签名者不同的 Signer,所以我们必须明确指定 Signer 是哪个账户。

错误:在 Solana Anchor 中未知的签名者

unknown signer 错误发生在交易的签名者与传递给 Signer 的 public key 不匹配时。

假设我们修改了测试,去除 .signers([newKeypair]) 规范。Anchor 将使用默认签名者,而默认签名者不会匹配我们的 newKeypair 钱包的 publicKey

去掉 .signers\(\[newKeypair\]\)

我们将得到以下错误:

错误:签名验证失败

类似地,如果我们没有明确传入 publicKey,Anchor 将默默地使用默认签名者:

未传入 publicKey

我们将得到以下 错误:未知的签名者

错误:未知的签名者

有些误导性地,Anchor 并不是因为没有指定签名者而说签名者是未知的。Anchor 能够推断出如果没有指定签名者,则将使用默认签名者。如果我们同时去除 .signers([newKeypair]) 代码和 fren: newKeypair.publicKey 代码,那么 Anchor 将为验证公共密钥进行签名,并且验证签名者的签名是否与公共密钥匹配。

以下代码将导致成功初始化,因为 Signer 的公共密钥和签署交易的账户都是 Anchor 默认签名者。

await program.methods.initialize().accounts({
  myStorage: myStorage
}).rpc();

other_write 初始化

Bob 可以写入 Alice 初始化的账户

下面我们展示一个带有初始化账户和写入功能的 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...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/