准备工作
在前面的文章中,我们已经安装好了solana的命令行工具,其中包括了秘钥生成以及编译工具等。有了 这些工具后,我们就可以开始进行相应的开发了
连接开发网
如果你还没有搭建自己的节点,为了省去节点的相关操作,可以先使用Solana提供的开发网进行开发, 因为开发网的配置和主网基本是一致的。
通过设置:
ubuntu@VM-0-12-ubuntu:~$ solana config set --url https://devnet.solana.com
Config File: /home/ubuntu/.config/solana/cli/config.yml
RPC URL: https://devnet.solana.com
WebSocket URL: wss://devnet.solana.com/ (computed)
Keypair Path: /home/ubuntu/.config/solana/id.json
这样设置了我们的默认API节点连到 https://devnet.solana.com ,其后面对应的节点网络是开发网。
准备账号
每个Onchain Program本质上其实也是一个Account,但是其被标记为”Executable:true”,也就是 可以执行的文件内容。为了存放这个可执行文件,我们需要创建一个账号,而创建账号需要另一个拥有 SOL的账号来付fee。所以首先我们来创建这个付fee的账号:
solana-keygen new
输入密码后,为将新创建的账号存在”/home/ubuntu/.config/solana/id.json”这个文件中, 后续如果solana命令没有指定 — key,那么默认就是用的这个文件。
然后查看这个私钥对应的公钥:
ubuntu@VM-0-12-ubuntu:~$ solana-keygen pubkey /home/ubuntu/.config/solana/id.json
7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf
然后在开发网上面申请SOL空投,从而获得需要的SOL:
ubuntu@VM-0-12-ubuntu:~$ solana airdrop 10 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf
Requesting airdrop of 10 SOL from 34.82.57.86:9900
10 SOL
来查询一下余额:
ubuntu@VM-0-12-ubuntu:~$ solana balance 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf
10 SOL
然后再创建一个账号:
solana-keygen new -o solana_memo_program.json
输入密码后,保存好key,这个后面再发布程序的时候使用。前面那个账号是发布程序的账号,这个账号 就是对应的程序的地址。
编译合约
在2020–10–22 之后的1.4.x版本,Solana提供了 “cargo-build-bpf”, 以及”cargo-test-bpf”等工具,用于直接将cargo工程编译出BPF文件。
在上面安装的Solana命令行中以及包含了”carg-build-bpf”。
这里checkout出官方的memo程序以做测试:
git clone https://github.com/solana-labs/solana-program-library.git
然后进入到目录中的solana-program-library/memo/program/ 并执行:
cargo build-bpf
他会调用上面的”cargo-build-bpf”程序,这个memo程序会记录发送请求中的内容在链上。
如果出现:
= note: /usr/bin/ld: cannot find Scrt1.o: No such file or directory
/usr/bin/ld: cannot find crti.o: No such file or directory
collect2: error: ld returned 1 exit status
报错,是因为缺少一些32位的依赖, 执行:
sudo apt install gcc-multilib
再次编译,编译成功得到:
To deploy this program:
$ solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so
这里用上我们前面创建的”solana_memo_program.json”:
solana-keygen pubkey ~/solana_memo_program.json
D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j
然后deploy:
solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so ~/solana_memo_program.json
{"programId":"D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j"}
这样就表示我们成功发布了一个程序到开发网上,程序地址为:D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j
打开,Solana浏览器,切换网络为开发网,可以查看程序:
4.测试合约
与链交互我们采用@solana/web3.js。
新建一个工程,并添加@solana/web3.js:
yarn init
yarn add @solana/web3.js
假设入口文件为src/index.js,整个目录如下:
├── package.json
├── src
│ └── index.js
然后填充内容:
var solana_web3 = require('@solana/web3.js');
function testMemo(connection, account){
const instruction = new solana_web3.TransactionInstruction({
keys: [],
programId:new solana_web3.PublicKey('D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j'),
data: Buffer.from('cztest'),
});
console.log("account:", account.publicKey.toBase58())
solana_web3.sendAndConfirmTransaction(
connection,
new solana_web3.Transaction().add(instruction),
[account],
{
skipPreflight: true,
commitment: "singleGossip",
},
).then(()=>{console.log("done")}).catch((e)=>{console.log("error",e)});
}function main() {
connection = new solana_web3.Connection("https://devnet.solana.com", 'singleGossip');
const account = new solana_web3.Account()
const lamports = 10*1000000000
connection.requestAirdrop(account.publicKey, lamports).then(()=>{
console.log("airdrop done")
testMemo(connection, account)
});
}main()
这里的逻辑是先创建一个账号,然后用这个账号申请空投10SOL,接着,用这个账号发送合约请求到 我们部署的”D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j”程序,结束后,我们来看 浏览器:
这里Memo程序在链上记录的信息为”637a74657374",是具体内容的hex结果:
>>> import binascii
>>> binascii.a2b_hex('637a74657374')
b'cztest'
和我们发送的值是一样的。
合约工程
上面我们是直接拿了别人的合约来编译部署的,那么如果我们要写一个自己的合约要怎么做呢?这里和普通的rust项目 一致,通过cargo来进行项目管理。
首先通过cargo创建一个项目,比如:
cargo new onchain_program
然后打开onchain_program/Cargo.toml,并增加修改:
[dependencies]
arrayref = "0.3.6"
num-derive = "0.3"
num-traits = "0.2"
num_enum = "0.5.1"
solana-program = "1.4.8"
thiserror = "1.0"[dev-dependencies]
solana-sdk = "1.4.8"[lib]
crate-type = ["cdylib", "lib"][package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
同时在这个目录下增加Xargo.toml:
[target.bpfel-unknown-unknown.dependencies.std]
features = []
增加bpf的跨平台编译支持。
接着编写合约内容,在src/lib.rs里面设置entrypoint:
#![deny(missing_docs)]//! A simple program that return success.#[cfg(not(feature = "no-entrypoint"))]
mod entrypoint;// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;
然后在entrypoint.rs里面书写合约内容:
//! Program entrypointuse solana_program::{
account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError,
pubkey::Pubkey,
};
use std::str::from_utf8;entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
Ok(())
}
这个合约的内容,是不做任何处理。直接返回成功。
函数”process_instruction”是整个合约入口,传入的是一个instruction结构。他包含了 表示执行指令的程序的:_program_id,所要执行使用的账户集:_accounts,以及序列化之后的 instruction_data部分。当认为执行成功时,通过Ok(())
返回成功,否则用Err(error)
返回出错。
此时onchain_program目录看起来如下:
在该目录下执行:
cargo build-bpf...To deploy this program:
$ solana deploy /home/ubuntu/solana/memo_test/onchain_program/target/deploy/onchain_program.so
即可构建出bpf的so文件了。
英文版请看这里
总结
Solana提供的Onchain Program开发的SDK以及构建工具,结合Cargo已经可以很方便的构建出目标 文件。只要弄明白了这个过程,就可以不被原来繁杂的脚本弄晕。其本质就是通过rustc结合LLVM将 rust程序编译成BPF格式的目标文件,然后将整个目标文件发布到链上。这其中需要注意的是不是所有的 rust特性都可以在开发中使用,具体可以参考Rust limitations。另外当前的主网还是1.3.x的版本, 如果想用1.3.x的编译工具链,则需要使用xargo-build.sh