在这个教程里,我们将学习如何开发Solana链上程序,内容包括创建Solana账号、 从测试链获取免费的SOL、编译部署与测试流程,并开发一个简单的Solana链上程序。 在教程结束部分提供了完整源码的下载链接。
在深入学习本教程之前,请确保已按照这个教程中的步骤设置了环境并安装了工具套件。 可以访问这里查看Solana RPC API文档。
如果你没有自己的节点也不要担心,Solana提供了与主网相同配置的devnet。所以在这里让我们首先将 API 端点 设置为开发链 - https://devnet.solana.com:
1 2 3 4 |
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) |
每个链上程序实际上都是一个Account,但它被标记为“Executable: true”,这意味着它是一个可执行文件。 为了存储这个文件,我们需要创建另一个可以支付费用的 Solana 账户:
1 |
solana-keygen new |
输入密码并确认后,新账号就保存在 /home/ubuntu/.config/solana/id.json
中,作为我们的默认密钥。 可以运行以下命令检查公钥:
1 2 |
ubuntu@VM-0-12-ubuntu:~$ solana-keygen pubkey /home/ubuntu/.config/solana/id.json 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf |
在Solana测试链上,可以运行以下命令获得一些免费的 SOL以便执行后续的操作:
1 2 3 |
ubuntu@VM-0-12-ubuntu:~$ solana airdrop 10 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf Requesting airdrop of 10 SOL from 34.82.57.86:9900 10 SOL |
让我们检查一下我们的余额:
1 2 |
ubuntu@VM-0-12-ubuntu:~$ solana balance 7FqW6xXE4sMmZSeVxFsoTr83He4MhhePvA1vRAv9zgQf 10 SOL |
哇,有钱。
然后我们创建另一个帐户,用于存储程序文件:
1 |
solana-keygen new -o solana_memo_program.json |
在这里,我们使用-o
选项将新的密钥对输出到新文件solana_memo_program.json 。
从 1.4.x 版本(2020-10-22 发布)开始,Solana 提供了cargo-build-bpf和cargo-test-bpf等工具, 帮助我们将 cargo 项目编译为 BPF 格式文件。
可以使用 Solana 官方团队提供的备忘录程序来试试这个。首先克隆仓库:
1 |
git clone https://github.com/solana-labs/solana-program-library.git |
然后跳到文件夹solana-program-library/memo/program/并运行:
1 |
cargo build-bpf |
这是我们上面提到的程序cargo-build-bpf的包装器。如果看到如下错误:
1 2 3 |
= 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位的依赖包,让我们通过安装来修复它:
1 |
sudo apt install gcc-multilib |
然后我们再次编译,得到:
1 2 |
To deploy this program: $ solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so |
好了,可以部署了,这里我们将它部署到我们创建的帐户并导出到文件solana_memo_program.json, 让我们先获取密钥:
1 2 |
solana-keygen pubkey ~/solana_memo_program.json D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j |
然后运行部署:
1 2 |
solana deploy /home/ubuntu/solana/solana-program-library/target/deploy/spl_memo.so ~/solana_memo_program.json {"programId":"D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j"} |
现在我们已经成功部署了一个程序到测试连,程序地址:D8Cnv1UcThay2WijWP4SQ8G683UuVsKPaZEU7TNVKW1j
为了验证命令行的结果,可以在 Solana explorer 上检查我们的程序,转到https://explorer.solana.com/ 并将网络更改为 Devnet,输入程序地址,可能会看到:
是的,我们的程序就在那里!
为了与我们的链上程序进行交互,这里我们需要老朋友@solana/web3.js。 让我们创建一个新的js项目并添加包@solana/web3.js:
1 2 |
yarn init yarn add @solana/web3.js |
然后在根文件夹和测试代码中创建一个类似index.js的入口 js 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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() |
让我们看看这里发生了什么。
首先连接到 devnet 端点,然后我们使用 solana_web3 提供的solana_web3.Account()
函数创建 一个新帐户,该帐户将用于稍后与我们的程序交互。
然后我们从 devnet 得到一些免费的SOL ,然后调用了这个testMemo函数,该函数向我们的程序发送一个交易, 参数作为数据流传入(这里我们传递一个字符串“ cztest ”)
现在,让我们再次使用资源管理器检查我们的Solana程序:
如上所示的交易,我们可以看到十六进制格式的数据,可以使用binascii 之类的工具来解码这些数据:
1 2 3 |
>>> import binascii >>> binascii.a2b_hex('637a74657374') b'cztest' |
在这里我们可以看到结果与我们发送给程序的字符串相同
我们刚刚尝试了一个从 Solana 官方 repo 中提取的程序,那么我们应该怎么做才能从头开始编写 一个Solana链上程序呢?作为一个普通的 Rust 项目,Solana 链上程序也是由 cargo 管理的。
首先,让我们使用 cargo 来启动我们的新项目:
1 |
cargo new onchain_program |
然后使用喜欢的编辑器打开onchain_program/Cargo.toml并为项目添加一些基本信息:
1 2 3 4 5 6 7 8 9 10 |
[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文件,用于在编译 bpf 文件时支持跨平台。 然后是时候自己动手了,让我们编写一些 Rust 代码。首先我们在src/lib.rs中添加一个入口点:
1 2 3 |
#![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 中添加一些代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
//! 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是我们在指令结构中传递的整个入口函数。它包含执行该指令所需的所有信息: _program_id表示程序,_accounts表示该指令中需要的所有帐户,instruction_data表示我们用来传递 其他参数的序列化数据流。当程序运行良好时,我们使用Ok(())返回成功,或者使用Err(error)返回失败。
如果你已经准备好,接下来就可以按之前所做的那样部署此程序:
1 2 |
cargo build-bpf...To deploy this program: $ solana deploy /home/ubuntu/solana/memo_test/onchain_program/target/deploy/onchain_program.so |
借助 Solana 提供的所有工具和 SDK,我们可以使用 cargo 轻松获取目标文件。一旦你了解了整个过程, 会发现它只是一个 Rust 项目,它使用rustrc提供的 LLVM 编译成 BPF 格式文件,然后我们将它部署在链上。 我想提一提的是,在开发 Solana 链上程序时,并不是所有的 Rust 特性都可以使用,详细信息可以 在这里找到。如果你使用 1.3.x 版本的工具套件,那么还需要xargo-build.sh。
教程代码可以从这里下载: example-helloworldhttps://github.com/solana-labs/example-helloworld#rust-limitations
solana-program-libraryhttps://github.com/solana-labs/solana-program-library cargo-build-bpfhttps://github.com/solana-labs/solana/tree/master/sdk/cargo-build-bpf