Solana Anchorのデバッグ方法

Solanaの開発ツール Anchor のデバッグ方法。

現象

$ anchor test を実行しても、Rustのデバッグ情報(dbgやprintln, msgなど)がターミナル上に出力できない。

原因

おそらく「$ anchor test」は、デプロイしたあとに、Anchor.tomlに記述されている「test = "yarn run mocha -t 1000000 tests/"」を実行してると思われる。
そのため、Rustのデバッグ情報はターミナル上には出力されず、JS側のデバッグ情報だけ出力されているっぽい。

Prgoram(Rust)のデバッグ対策

(案1)ブロックチェーンのプログラムログに出力

Rustの「msg!」を記述しておくと、ブロックチェーンのProgram Logsに出力できる。

「$ anchor test」はbuild→deploy→testまで実行され、「$ anchor test」を実行したディレクトリに「.anchor/program-logs/[declare_idのアドレス]」が出力され、そこでデプロイログが確認できる。デフォルトがlocalnetのため、気軽に実行可能。

たとえば、以下のように「msg!("msg here!!");」を入れておくと、

lib.rs

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod basic_0 {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
        msg!("msg here!!");
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

ターミナルでanchor testすると、Program Logに「msg here!!」が出力される。

% pwd
/Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0


% anchor test
BPF SDK: /Users/user/.local/share/solana/install/releases/1.8.5/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.26s
cargo-build-bpf child: /Users/user/.local/share/solana/install/releases/1.8.5/solana-release/bin/sdk/bpf/dependencies/bpf-tools/llvm/bin/llvm-readelf --dyn-symbols /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0.so

To deploy this program:
  $ solana program deploy /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0.so
The program address will default to this keypair (override with --program-id):
  /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0-keypair.json
yarn run v1.22.17
$ /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/node_modules/.bin/mocha -t 1000000 tests/


  basic-0
    ✔ Uses the workspace to invoke the initialize instruction (362ms)


  1 passing (365ms)

✨  Done in 5.54s.


% cat .anchor/program-logs/Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS.basic_0.log
Streaming transaction logs mentioning Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS. Confirmed commitment
Transaction executed in slot 2:
  Signature: 4Jd5s5dnEWEuEbUkTdwxNULiDwDvV71VZhsE6C1Xp4Wr3MbnEUJhRWHGomWTeNSUFCc3SXCTu3TNcBsaG2QgUiZu
  Status: Ok
  Log Messages:
    Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS invoke [1]
    Program log: Instruction: Initialize
    Program log: msg here!!
    Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS consumed 396 of 200000 compute units
    Program Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS success

(参考)なお、Solana Explorerだと最下部にProgram Logsが確認できる → Solana Explorer Devnet

(案2)cargo testで出力

「$ cargo test」が利用できるため、普通にRustでtestを記述する方法。
Anchor tutorial basic-0 を使った記述例。

lib.rs

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod basic0 {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
        Ok(())
    }
}

pub fn greeting() {
    let reply = "hello";
    dbg!(reply);
}

#[derive(Accounts)]
pub struct Initialize {}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_sanity() {
        greeting();

        let exp = "smile";
        dbg!(exp);
    }
}

Anchorのサンプルをそのままcargo testすると、「ambiguous name error[E0659]: basic_0 is ambiguous (name vs any other name during import resolution)」と怒られるため、リネームが必要。「basic_0 → basic0」のリネームでOK。

ターミナルで実行

% cargo test -- --nocapture
    Finished test [unoptimized + debuginfo] target(s) in 0.07s
     Running unittests (target/debug/deps/basic_0-a269b2199b5b3dd4)

running 2 tests
[programs/basic-0/src/lib.rs:15] reply = "hello"
[programs/basic-0/src/lib.rs:30] exp = "smile"
test test_id ... ok
test test::test_sanity ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

   Doc-tests basic_0

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Client(JS)のデバッグ対策

(案1)anchor testでconsole.log

JSファイルに「console.log」を記述しておくだけで、「$ anchor test」を実行すると、ターミナルに情報が出力される。

basic-0.js

const anchor = require("@project-serum/anchor");

describe("basic-0", () => {
  // Configure the client to use the local cluster.
  anchor.setProvider(anchor.Provider.local());

  it("Uses the workspace to invoke the initialize instruction", async () => {
    // #region code
    // Read the deployed program from the workspace.
    const program = anchor.workspace.Basic0;
    console.log(program);

    // Execute the RPC.
    await program.rpc.initialize();
    // #endregion code
  });
});

ターミナルで実行

% anchor test
BPF SDK: /Users/user/.local/share/solana/install/releases/1.8.5/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
   Compiling basic-0 v0.1.0 (/Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/programs/basic-0)
    Finished release [optimized] target(s) in 1.13s
cargo-build-bpf child: /Users/user/.local/share/solana/install/releases/1.8.5/solana-release/bin/sdk/bpf/scripts/strip.sh /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/bpfel-unknown-unknown/release/basic_0.so /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0.so
cargo-build-bpf child: /Users/user/.local/share/solana/install/releases/1.8.5/solana-release/bin/sdk/bpf/dependencies/bpf-tools/llvm/bin/llvm-readelf --dyn-symbols /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0.so

To deploy this program:
  $ solana program deploy /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0.so
The program address will default to this keypair (override with --program-id):
  /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/basic-0/target/deploy/basic_0-keypair.json
yarn run v1.22.17
$ /Users/user/Desktop/blockchain/anchor-master/examples/tutorial/node_modules/.bin/mocha -t 1000000 tests/


  basic-0
Program {
  _idl: {
    version: '0.0.0',
    name: 'basic_0',
    instructions: [ [Object] ],
    metadata: { address: 'Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS' }
  },
  _provider: Provider {
    connection: Connection {
      _commitment: 'recent',
      _confirmTransactionInitialTimeout: undefined,
      _rpcEndpoint: 'http://localhost:8899',
      _rpcWsEndpoint: 'ws://localhost:8900/',
      _rpcClient: [ClientBrowser],
      _rpcRequest: [Function (anonymous)],
      _rpcBatchRequest: [Function (anonymous)],
      _rpcWebSocket: [Client],
      _rpcWebSocketConnected: false,
      _rpcWebSocketHeartbeat: null,
      _rpcWebSocketIdleTimeout: null,
      _disableBlockhashCaching: false,
      _pollingBlockhash: false,
      _blockhashInfo: [Object],
      _accountChangeSubscriptionCounter: 0,
      _accountChangeSubscriptions: {},
      _programAccountChangeSubscriptionCounter: 0,
      _programAccountChangeSubscriptions: {},
      _rootSubscriptionCounter: 0,
      _rootSubscriptions: {},
      _signatureSubscriptionCounter: 0,
      _signatureSubscriptions: {},
      _slotSubscriptionCounter: 0,
      _slotSubscriptions: {},
      _logsSubscriptionCounter: 0,
      _logsSubscriptions: {},
      _slotUpdateSubscriptionCounter: 0,
      _slotUpdateSubscriptions: {}
    },
    wallet: NodeWallet { payer: [Keypair] },
    opts: { preflightCommitment: 'recent', commitment: 'recent' }
  },
  _programId: PublicKey {
    _bn: <BN: da075cb2ff5ec6817613de530b692a8735477769da47430cbd8154335c4a8327>
  },
  _coder: Coder {
    instruction: InstructionCoder {
      idl: [Object],
      ixLayout: [Map],
      sighashLayouts: [Map]
    },
    accounts: AccountsCoder { accountLayouts: Map(0) {} },
    events: EventCoder { layouts: Map(0) {} }
  },
  _events: EventManager {
    _programId: PublicKey {
      _bn: <BN: da075cb2ff5ec6817613de530b692a8735477769da47430cbd8154335c4a8327>
    },
    _provider: Provider {
      connection: [Connection],
      wallet: [NodeWallet],
      opts: [Object]
    },
    _eventParser: EventParser { coder: [Coder], programId: [PublicKey] },
    _eventCallbacks: Map(0) {},
    _eventListeners: Map(0) {},
    _listenerIdCount: 0
  },
  rpc: { initialize: [AsyncFunction: rpc] },
  instruction: { initialize: [Function: ix] { accounts: [Function (anonymous)] } },
  transaction: { initialize: [Function: txFn] },
  account: {},
  simulate: { initialize: [AsyncFunction: simulate] },
  state: undefined
}
    ✔ Uses the workspace to invoke the initialize instruction (341ms)


  1 passing (344ms)

✨  Done in 5.55s.