将复杂类型传递给锚点 rpc 失败

Passing complex types to anchor rpc fails

我修改了基本教程示例以传​​递结构,但是当我 运行“锚点测试”时它失败并显示 TypeError: Blob.encode[data] requires (length 4) Buffer as src 当我搜索这个错误时,我在互联网上找不到任何东西。

附上代码。如果我以某种方式弄错了,请告诉我。 谢谢,

Lib.rs


use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod basic_1 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }

    pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }

    pub fn add_signatory(ctx: Context<Update>, signatory: Signatory) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        //my_account.data = data;
        my_account.signatories.push(signatory);
        Ok(())
    }

    pub fn add_signatories(ctx: Context<Update>, signatories: Vec<Signatory>) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        //my_account.data = data;
        my_account.signatories = signatories;
        Ok(())
    }


}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 100)]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[account]
pub struct MyAccount {
    pub data: u64,
    pub project_id: u64,
    pub project_name: Vec<u8>,
    pub signatories: Vec<Signatory>,
}

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Signatory {
    pub name: Vec<u8>,
    pub public_key: Pubkey,
}

基本-1.js

const assert = require("assert");
const anchor = require("@project-serum/anchor");
const { SystemProgram } = anchor.web3;

describe("basic-1", () => {
  // Use a local provider.
  const provider = anchor.AnchorProvider.local();

  // Configure the client to use the local cluster.
  anchor.setProvider(provider);

  it("Creates and initializes an account in a single atomic transaction (simplified)", async () => {
    // #region code-simplified
    // The program to execute.
    const program = anchor.workspace.Basic1;

    // The Account to create.
    const myAccount = anchor.web3.Keypair.generate();

    // Create the new account and initialize it with the program.
    // #region code-simplified
    await program.rpc.initialize(new anchor.BN(1234), {
      accounts: {
        myAccount: myAccount.publicKey,
        user: provider.wallet.publicKey,
        systemProgram: SystemProgram.programId,
      },
      signers: [myAccount],
    });
    // #endregion code-simplified

    // Fetch the newly created account from the cluster.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was initialized.
    assert.ok(account.data.eq(new anchor.BN(1234)));

    // Store the account for the next test.
    _myAccount = myAccount;
  });

  it("Updates a previously created account", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;

    // Invoke the update rpc.
    await program.rpc.update(new anchor.BN(4321), {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.ok(account.data.eq(new anchor.BN(4321)));

    // #endregion update-test
  });

  it("add a single signatory", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;
    const pubkey1 = anchor.web3.Keypair.generate();
    const pubkey2 = anchor.web3.Keypair.generate();
    const signatory1 =
    {
      name: "matt",
      public_key: pubkey1,
    };

    // Invoke the update rpc.
    await program.rpc.addSignatory(signatory1, {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.ok(account.data.eq(new anchor.BN(4321)));

    // #endregion update-test
  });

  it("add multiple signatories", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;
    const pubkey1 = anchor.web3.Keypair.generate();
    const pubkey2 = anchor.web3.Keypair.generate();

    const signatories1 = [
      {
        name: "matt",
        public_key: pubkey1,
      },
      {
        name: "smith",
        public_key: pubkey2,
      },
    ];


    // Invoke the update rpc.
    await program.rpc.addSignatories(signatories1, {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);

    // Check it's state was mutated.
    assert.ok(account.data.eq(new anchor.BN(4321)));

    // #endregion update-test
  });


});

锚点测试输出

abcd@abcd:~/code/anchor/examples/tutorial/basic-1$ anchor test
BPF SDK: /home/abcd/.local/share/solana/install/releases/1.10.8/solana-release/bin/sdk/bpf
cargo-build-bpf child: rustup toolchain list -v
cargo-build-bpf child: cargo +bpf build --target bpfel-unknown-unknown --release
    Finished release [optimized] target(s) in 0.17s
cargo-build-bpf child: /home/abcd/.local/share/solana/install/releases/1.10.8/solana-release/bin/sdk/bpf/dependencies/bpf-tools/llvm/bin/llvm-readelf --dyn-symbols /home/abcd/code/anchor/examples/tutorial/basic-1/target/deploy/basic_1.so

To deploy this program:
  $ solana program deploy /home/abcd/code/anchor/examples/tutorial/basic-1/target/deploy/basic_1.so
The program address will default to this keypair (override with --program-id):
  /home/abcd/code/anchor/examples/tutorial/basic-1/target/deploy/basic_1-keypair.json

Found a 'test' script in the Anchor.toml. Running it as a test suite!

Running test suite: "/home/abcd/code/anchor/examples/tutorial/basic-1/Anchor.toml"

yarn run v1.22.18
$ /home/abcd/code/anchor/examples/tutorial/node_modules/.bin/mocha -t 1000000 tests/


  basic-1
    ✔ Creates and initializes an account in a single atomic transaction (simplified) (250ms)
    ✔ Updates a previously created account (411ms)
    1) add a single signatory
    2) add multiple signatories


  2 passing (698ms)
  2 failing

  1) basic-1
       add a single signatory:
     TypeError: Blob.encode[data] requires (length 4) Buffer as src
      at Blob.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:2321:13)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at WrappedLayout.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/@project-serum/borsh/dist/lib/index.js:67:28)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at BorshInstructionCoder._encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/coder/borsh/instruction.js:91:28)
      at BorshInstructionCoder.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/coder/borsh/instruction.js:76:21)
      at /home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/index.js:41:100
      at ix (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/instruction.js:50:23)
      at txFn (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/transaction.js:16:20)
      at Object.rpc [as addSignatory] (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/rpc.js:9:24)
      at Context.<anonymous> (tests/basic-1.js:82:23)
      at processImmediate (node:internal/timers:466:21)

  2) basic-1
       add multiple signatories:
     TypeError: Blob.encode[data] requires (length 4) Buffer as src
      at Blob.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:2321:13)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at WrappedLayout.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/@project-serum/borsh/dist/lib/index.js:67:28)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at /home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1113:25
      at Array.reduce (<anonymous>)
      at Sequence.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1112:22)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at WrappedLayout.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/@project-serum/borsh/dist/lib/index.js:67:28)
      at Structure.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/node_modules/buffer-layout/lib/Layout.js:1263:26)
      at BorshInstructionCoder._encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/coder/borsh/instruction.js:91:28)
      at BorshInstructionCoder.encode (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/coder/borsh/instruction.js:76:21)
      at /home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/index.js:41:100
      at ix (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/instruction.js:50:23)
      at txFn (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/transaction.js:16:20)
      at Object.rpc [as addSignatories] (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/rpc.js:9:24)
      at Context.<anonymous> (tests/basic-1.js:120:23)
      at processImmediate (node:internal/timers:466:21)



error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

basic_1.json

{
  "version": "0.1.0",
  "name": "basic_1",
  "instructions": [
    {
      "name": "initialize",
      "accounts": [
        {
          "name": "myAccount",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "user",
          "isMut": true,
          "isSigner": true
        },
        {
          "name": "systemProgram",
          "isMut": false,
          "isSigner": false
        }
      ],
      "args": [
        {
          "name": "data",
          "type": "u64"
        }
      ]
    },
    {
      "name": "update",
      "accounts": [
        {
          "name": "myAccount",
          "isMut": true,
          "isSigner": false
        }
      ],
      "args": [
        {
          "name": "data",
          "type": "u64"
        }
      ]
    },
    {
      "name": "addSignatory",
      "accounts": [
        {
          "name": "myAccount",
          "isMut": true,
          "isSigner": false
        }
      ],
      "args": [
        {
          "name": "signatory",
          "type": {
            "defined": "Signatory"
          }
        }
      ]
    },
    {
      "name": "addSignatories",
      "accounts": [
        {
          "name": "myAccount",
          "isMut": true,
          "isSigner": false
        }
      ],
      "args": [
        {
          "name": "signatories",
          "type": {
            "vec": {
              "defined": "Signatory"
            }
          }
        }
      ]
    }
  ],
  "accounts": [
    {
      "name": "MyAccount",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "data",
            "type": "u64"
          },
          {
            "name": "projectId",
            "type": "u64"
          },
          {
            "name": "projectName",
            "type": "bytes"
          },
          {
            "name": "signatories",
            "type": {
              "vec": {
                "defined": "Signatory"
              }
            }
          }
        ]
      }
    }
  ],
  "types": [
    {
      "name": "Signatory",
      "type": {
        "kind": "struct",
        "fields": [
          {
            "name": "name",
            "type": "bytes"
          },
          {
            "name": "publicKey",
            "type": "publicKey"
          }
        ]
      }
    }
  ],
  "metadata": {
    "address": "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
  }
}

更新 1

按照下面 Jon C 的建议将类型更改为字符串后 - 测试 运行s。 但是,如果我两次调用 addSignatory 方法,它会失败并显示错误“错误:由帐户引起的 AnchorError:my_account。错误代码:AccountDidNotSerialize。错误编号:3004。错误消息:无法序列化帐户。”。

已更新基础-1.js

it("add a single signatory", async () => {
    const myAccount = _myAccount;

    // #region update-test

    // The program to execute.
    const program = anchor.workspace.Basic1;
    const pubkey1 = anchor.web3.Keypair.generate();
    const pubkey2 = anchor.web3.Keypair.generate();
    const signatory1 =
    {
      name: "matt",
      public_key: pubkey1,
    };

    // Invoke the update rpc.
    await program.rpc.addSignatory(signatory1, {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account = await program.account.myAccount.fetch(myAccount.publicKey);
    //assert.ok(account.signatories.len().eq(new anchor.BN(1)));
    assert.equal(account.signatories.length, 1);


    const signatory2 =
    {
      name: "smith",
      public_key: pubkey2,
    };

    // Invoke the update rpc.
    await program.rpc.addSignatory(signatory2, {
      accounts: {
        myAccount: myAccount.publicKey,
      },
    });

    // Fetch the newly updated account.
    const account2 = await program.account.myAccount.fetch(myAccount.publicKey);
    //assert.ok(account.signatories.len().eq(new anchor.BN(1)));
    assert.equal(account2.signatories.length, 2);



    // Check it's state was mutated.
    assert.ok(account.data.eq(new anchor.BN(4321)));

    // #endregion update-test
  });

锚点测试输出

1) basic-1
       add a single signatory:
     Error: AnchorError caused by account: my_account. Error Code: AccountDidNotSerialize. Error Number: 3004. Error Message: Failed to serialize the account.
      at Function.parse (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/error.js:138:20)
      at translateError (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/error.js:224:37)
      at Object.rpc [as addSignatory] (/home/abcd/code/anchor/examples/tutorial/node_modules/@project-serum/anchor/dist/cjs/program/namespace/rpc.js:18:53)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async Context.<anonymous> (tests/basic-1.js:101:5)

更新 2

所以我想出了更多的东西,感谢@JonC 的提示帮助我前进。

  1. 要调用 Rust 方法,您需要将方法名称更改为 javascript 中的 snakecase,因此 add_signatory 将从 JS 中调用为 addSignatory。
  2. 在作为参数传递给 rust 方法之前,您需要在 JS 中的字符串周围添加引号。所以需要保证在JS代码中字符串变量用'"'填充。
  3. 我仍然不确定为什么这不起作用,但是将 public 键作为字符串从 JS 发送到 Rust 会导致锚点测试失败并出现“帐户无法序列化”错误,如此处所述。仍在尝试解决这个问题。

您的签名者中的 name 字段在后端定义为 Vec<u8>,但您从前端传递了一个字符串。尝试将 Signatory 类型更新为:

#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Signatory {
    pub name: String,
    pub public_key: Pubkey,
}

这样,序列化程序就会知道将名称正确编码为字符串,而不是字节。