Solanaの開発ツール Anchor のデバッグ方法。
Agenda
現象
$ 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!!");」を記述すると、固定文字が出力され、「msg!("{:?}", 変数);」を記述すると、変数が出力される。
lib.rs
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
mod basic_0 {
use super::*;
let variable = 123456789;
pub fn initialize(_ctx: Context<Initialize>) -> ProgramResult {
msg!("msg here!!");
msg!("{:?}", variable);
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize {}
ターミナルでanchor testすると、Program Logに「msg here!!」と「変数(今回のケースだと123456789)」が出力される。
% 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 log: 123456789
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
(案3)localnetでSolana Explorerに出力
anchorプログラムをlocalnetにデプロイしてから、Solana Explorerにアクセスする方法。Solana ExplorerでアカウントもあわせてGUI上でトレースできるため便利。
localhostに設定:
% solana config set --url localhost
ローカルのバリデータを起動:
% solana-test-validator
デプロイしてProgram Idを取得:
% anchor deploy
~~~
Program Id: EXi7tnp3oYjUvpoDm3Bxc63KhYKS1nJW5xa8zyuZBESw
~~~
Anchor.toml と lib.rs の declare_id を上記Program Idに書き換えてから、build:
% anchor build
anchor testを実行して、トランザクションのsignatureを取得:
% anchor test
tx => 2nDVg6C3bufg6Zw9McsnwipjEwZn9ahecj3wXzPR86gjg9Pr2EVbdTLxGGCDehRMkh1dFzKqCtR9oBQzYzZd5yGU
Solana Explorerでトランザクションを検索:
Solana Explorerでlocalnetのトランザクションを閲覧する方法
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.
参考
デバッグ
Solana Developing with Rust - Logging
msg! has two forms:
msg!("A string");
or
msg!(0_64, 1_64, 2_64, 3_64, 4_64);
Both forms output the results to the program logs. If a program so wishes they can emulate println! by using format!:
msg!("Some variable: {:?}", variable);
言語別にデバッグ
「Rust x Anchor x TypeScript」の組み合わせになるため、もし、RustまたはTypeScriptだけのデバッグでよければ、個別にファイルを作成してデバッグもできる。
- Rustだったら cargo new してデバッグ埋めてプログラムを書いて cargo run
- TypeScriptだったら tsc init してデバッグ埋めてプログラムを書いて ts-node
たとえば、エラーは出ないのに「Rust x Anchor x TypeScript」で統合して動かすとエラーになり、デバッグもうまくできない、という場合は手間になるが、言語別に書いてデバッグするのがよさそう。