Solana Anchorで「Creates and initializes an account in a single atomic transaction (simplified)」エラー

Solanaは何をするにもアカウントが発行されるので、Anchorを使っていると、以下のように「#[account(init)」でアカウントの初期化を行うことになる。

    #[account(init, payer = user, space = 8 + 8)]
    pub crud_account: Account<'info, CrudAccount>,

データ格納用の「#[account]」を複数作成して、どちらも同じPublicKeyを使用したらどうなるか検証したときのメモ。以下のようなイメージ。

#[account]
pub struct CrudAccount {
    pub data: u64,
}

#[account]
pub struct CrudAccount2 {
    pub data: u64,
}

現象

anchor testを実施したところ、「Creates and initializes an account in a single atomic transaction (simplified)」エラーが出力。

  CRUD
data:  1234
CwarBz4jUs4Sr7gFz7ZkpLVrR6unaUPybzzXnLpqjQY
4NyFApAgLURknM4xgkRYaQSHcFBG3D3xnfeEw6hVdxhdVGThS56RHc3kmcxJwWHDW9dFFNxaeH71oJnys3oAZyL5
    ✔ 1: Creates and initializes an account in a single atomic transaction (simplified) (506ms)
Transaction simulation failed: Error processing Instruction 0: custom program error: 0x0
    Program EXi7tnp3oYjUvpoDm3Bxc63KhYKS1nJW5xa8zyuZBESw invoke [1]
    Program log: Instruction: Create2
    Program 11111111111111111111111111111111 invoke [2]
    Allocate: account Address { address: CwarBz4jUs4Sr7gFz7ZkpLVrR6unaUPybzzXnLpqjQY, base: None } already in use
    Program 11111111111111111111111111111111 failed: custom program error: 0x0
    Program EXi7tnp3oYjUvpoDm3Bxc63KhYKS1nJW5xa8zyuZBESw consumed 4892 of 1400000 compute units
    Program EXi7tnp3oYjUvpoDm3Bxc63KhYKS1nJW5xa8zyuZBESw failed: custom program error: 0x0
    1) 2: Creates and initializes an account in a single atomic transaction (simplified)


  1 passing (612ms)
  1 failing

  1) CRUD
       2: Creates and initializes an account in a single atomic transaction (simplified):
     Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: custom program error: 0x0
      at Connection.sendEncodedTransaction (node_modules/@solana/web3.js/src/connection.ts:3964:13)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at Connection.sendRawTransaction (node_modules/@solana/web3.js/src/connection.ts:3921:20)
      at sendAndConfirmRawTransaction (node_modules/@solana/web3.js/src/util/send-and-confirm-raw-transaction.ts:27:21)
      at Provider.send (node_modules/@project-serum/anchor/src/provider.ts:118:18)
      at Object.rpc [as create2] (node_modules/@project-serum/anchor/src/program/namespace/rpc.ts:25:23)



error Command failed with exit code 1.

ソースコードは以下。

lib.rs

use anchor_lang::prelude::*;

declare_id!("EXi7tnp3oYjUvpoDm3Bxc63KhYKS1nJW5xa8zyuZBESw");

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

    pub fn create(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let crud_account = &mut ctx.accounts.crud_account;
        crud_account.data = data;
        msg!("data: {}", data);
        Ok(())
    }

    pub fn create2(ctx: Context<Initialize2>, data: u64) -> Result<()> {
        let crud_account2 = &mut ctx.accounts.crud_account2;
        crud_account2.data = data;
        msg!("data: {}", data);
        Ok(())
    }
}

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

#[account]
pub struct CrudAccount {
    pub data: u64,
}

#[derive(Accounts)]
pub struct Initialize2<'info> {
    #[account(init, payer = user, space = 8 + 8)]
    pub crud_account2: Account<'info, CrudAccount2>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct CrudAccount2 {
    pub data: u64,
}

myapp.ts

import * as anchor from "@project-serum/anchor";
import { Program } from "@project-serum/anchor";
import { Crud } from "../target/types/crud";
import { PublicKey, SystemProgram } from '@solana/web3.js';
import { assert } from "chai";

describe("CRUD", () => {
  const provider = anchor.Provider.local();
  const connection = provider.connection
  anchor.setProvider(provider);

  const program = anchor.workspace.Crud as Program<Crud>;

  const crudAccount = anchor.web3.Keypair.generate();

  it("1: Creates and initializes an account in a single atomic transaction (simplified)", async () => {

    const tx = await program.rpc.create(
      new anchor.BN(1234), // args: data
      {
        accounts: { // args: ctx
          crudAccount: crudAccount.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
      },
      signers: [crudAccount],
    });

    const account = await program.account.crudAccount.fetch(crudAccount.publicKey);

    assert.ok(account.data.eq(new anchor.BN(1234)));
    console.log("data: ", account.data.toString());

    console.log(crudAccount.publicKey.toString());
    console.log(tx);
  });

  it("2: Creates and initializes an account in a single atomic transaction (simplified)", async () => {

    const tx2 = await program.rpc.create2(
      new anchor.BN(1234), // args: data
      {
        accounts: { // args: ctx
          crudAccount2: crudAccount.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
      },
      signers: [crudAccount],
    });

    console.log(crudAccount2.publicKey.toString());
    console.log(tx2);

    // const account = await program.account.crudAccount.fetch(crudAccount.publicKey);
    //
    // assert.ok(account.data.eq(new anchor.BN(1234)));
    // console.log("data: ", account.data.toString());

    // Store the account for the next test.
  });
});

原因

初期化したアカウントのPublicKeyが重複しているため。

今回の場合は、データ格納用の CrudAccount と CrudAccount2 に以下「crudAccount.publicKey」を使用した。

  const crudAccount = anchor.web3.Keypair.generate();
crudAccount.publicKeyをどちらも「#[account(init)」を使用したため。

#[account]
pub struct CrudAccount {
    pub data: u64,
}

#[account]
pub struct CrudAccount2 {
    pub data: u64,
}

対応

PublicKeyをユニークに変更する(PublicKeyの新規作成)。

  const crudAccount2 = anchor.web3.Keypair.generate();

    〜〜〜省略〜〜〜

    const tx2 = await program.rpc.create2(
      new anchor.BN(1234), // args: data
      {
        accounts: { // args: ctx
          crudAccount2: crudAccount2.publicKey,
          user: provider.wallet.publicKey,
          systemProgram: SystemProgram.programId,
      },
      signers: [crudAccount],
    });