
前面Solana智能合约开发:HelloWorld 合约 我们的合约写好了,也可以正常编译了,那么怎么确定我们的合约能正常工作呢?
在前面的文章Solana开发教程:搭建开发环境 我们介绍了solana1.4.x提供了一个localnet.sh的脚步可以搭建一个本地的solana节点,然后在上面发布合约。 但是从solana1.5.0以后,官方又为我们增加了一个”solana-test-validator”命令,方便我们启动自己的测试节点。
有了测试节点后,我们可以将合约发布到我们自己的本地测试节点上,然后通过观察节点输出的日志,来进行合约内容的 调试。
启动测试节点
首先我们去到solana的github 下载最新的1.5.0版本,然后在本地解压,并设置bin目录到Path路径。或者直接在bin目录下执行
ubuntu@VM-0-3-ubuntu:~/solana/localtest$ solana-test-validator -h
solana-test-validator 1.5.0 (src:981294cb; feat:2255750762)
Test ValidatorUSAGE:
solana-test-validator [FLAGS] [OPTIONS] --ledger <DIR>FLAGS:
-h, --help Prints help information
--log Log mode: stream the validator log
-q, --quiet Quiet mode: suppress normal output
-r, --reset Reset the ledger to genesis if it exists. By default the validator will resume an existing ledger
(if present)
-V, --version Prints version informationOPTIONS:
--bpf-program <ADDRESS BPF_PROGRAM.SO>... Add a BPF program to the genesis configuration
-C, --config <PATH>
Configuration file to use [default: /home/ubuntu/.config/solana/cli/config.yml] -l, --ledger <DIR> Use DIR as ledger location [default: test-ledger]
--mint <PUBKEY>
Address of the mint account that will receive tokens created at genesis [default: client keypair] --rpc-port <PORT>
Use this port for JSON RPC and the next port for the RPC websocket [default: 8899]
这些选项中,通过 — log可以打印节点运行日志;通过-C指定使用的config文件, 默认为”~/.config/solana/cli/config.yml”;通过-l指定节点区块信息存储目录,不指定时为当前目录; — rpc-port指定了RPC的端口,默认为8899以及websocket的8900。
这里我们以这样的命令来启动本地测试节点:
nohup solana-test-validator --log > solana.log 2>&1 &
这里首先通过”nohup” 加上最后的”&”,让节点程序在后台运行。然后通过” — log > solana.log”选项, 让节点打印出日志。并通过2>&1
将错误日志也重定向到文件中。
之后我们通过 :
tail -f solana.log
就可以查看滚动的节点日志了。如果再加上”grep”和我们日志的中的关键字,就可以过滤出我们自己合约中的关键 日志了。
发布合约
在编译好我们的合约后,solana会提示我们:
To deploy this program:
$ solana deploy /xxx/onchain_program/target/deploy/helloworld.so
但是如果这时我们执行,可能会报错,因为这里还没有发布程序的账号,并且这个账号是需要有SOL代币的,用于 发布合约的消耗。因此我们首先创建账号
solana-keychan new
输入密码后,记好你的公钥,如果不记得,可以用:
solana-keygen pubkey ~/.config/solana/id.json
4n3CDb6jtrbsChMVUSbnARknv1S6wCTN1bRWqopfH35B
来找回,然后申请空投:
solana airdrop 10 4n3CDb6jtrbsChMVUSbnARknv1S6wCTN1bRWqopfH35B
Requesting airdrop of 10 SOL from 127.0.0.1:9900
10 SOL
空投10个SOL到这个账号,接着我们就可以用上面提示的命令发布程序了:
solana deploy /xxx/onchain_program/target/deploy/helloworld.so
{"programId":"AffdN6VX2wjyDYCHbZa2wPGcpyRzNPPn2ostTw1sChZ9"}
空投前,记得设置RPC节点为本地节点:
solana config set --url http://localhost:8899
DApp RPC交互
要想测试合约,我们必须要通过API的RPC接口来和节点进行交互。这里我们选择了JS的API solana-web3.js, solana同时也提供 了Rust的SDK,也可以用Rust写个命令行客户端。
JS的demo可以参见:helloworld_demo
这是一个react的项目,pull下来后,执行:
yarn install
yarn start
就可以运行了。
这里我们主要来看”hello”指令是怎么执行的。在App.js里面:
let messageNeeded = await this.connection.getMinimumBalanceForRentExemption(Layout.messagSpace);const trxi0 = SystemProgram.createAccount({
fromPubkey: this.playerAccount.publicKey,
newAccountPubkey: this.messageAccount.publicKey,
lamports: messageNeeded,
space: Layout.messagSpace,
programId: this.programID,
});console.log("message:", this.messageAccount.publicKey.toBase58());
let trxi = HelloWorld.createHelloInstruction(
this.playerAccount.publicKey,
this.messageAccount.publicKey,
this.programID,
"hello world!",
);const transaction = new Transaction();
transaction.add(trxi0);
transaction.add(trxi);let signers= [this.playerAccount, this.messageAccount];
sendAndConfirmTransaction(this.connection, transaction, signers, {
skipPreflight: false,
commitment: 'recent',
preflightCommitment: 'recent',
}).then(()=>{
console.log("done hello");
}).catch((e)=>{
console.log("error:", e);
})
首先通过connection.getMinimumBalanceForRentExemptio
来查询,创建一个我们存的Message大小的 Account需要多少花费,然后通过系统的创建账号指令,创建一个Instruction,也就是上面的trxi0。
接着我们通过HelloWorld.js里面封装的createHelloInstruction
创建我们前面文章中需要的instruction:
static createHelloInstruction(
playerAccountKey,
messageAccountKey,
programID,
message,
) { const dataLayout = BufferLayout.struct([
BufferLayout.u8("i"),
BufferLayout.blob(message.length,"message"),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
i:0, // hello
message: Buffer.from(message, 'utf8'),
},
data,
);
let keys = [
{pubkey: playerAccountKey, isSigner: true, isWritable: true},
{pubkey: messageAccountKey, isSigner: true, isWritable: true},
]; const trxi = new TransactionInstruction({
keys,
programId: programID,
data,
});
return trxi;
}
这里将keys里面设置了用户的账号和上面创建的message的账号,data则是按照前面文中的:
+-----------------------------------+
Hello: | 0 | message |
+-----------------------------------+
填入的内容。
最后创建transaction并进行发送。
这是我们通过上面的tail命令:
tail -f solana.log |grep hello
然后在界面上触发hello按钮,观看打印:
[2020-12-29T14:56:58.304430806Z DEBUG solana_runtime::message_processor] Program log: hello-world: HelloWorld msg:hello world!
[2020-12-29T14:56:58.304451252Z DEBUG solana_runtime::message_processor] Program log: before unpack hello
[2020-12-29T14:56:58.304475950Z DEBUG solana_runtime::message_processor] Program log: after unpack hello
[2020-12-29T14:56:58.304484573Z DEBUG solana_runtime::message_processor] Program log: before pack hello
[2020-12-29T14:56:58.304577373Z DEBUG solana_runtime::message_processor] Program log: pack into slice msg:hello world!
[2020-12-29T14:56:58.304603157Z DEBUG solana_runtime::message_processor] Program log: after pack hello
这样就可以通过日志来进行相应的调试了
总结
当前,Solana还没法像一般程序开发一样下断点进行调试,但是我们可以通过打日志的方式,对合约执行逻辑进行调试,但是需要注意的是,在发布的合约时,要精简一下日志,因为打日志是有 资源消耗的,并且尽量少使用,不使用format!
,这个宏会消耗大量的计算单元。
除此之外,还可以通过rust的单元测试来对我们的一般逻辑进行测试,这个时候是可以通过断点以及普通的rust程序 调试过程来进行调试的。