nox.im · All Posts · All in Solana · All in Rust
The design of Solana shows a deep understanding of distributed systems, operating systems and hardware. This article is the first in a series of communicating from the Web3 frontends via JSON RPC to Solana nodes, building and deploying on-chain programs and understanding the fundamental concepts.
Here, I’m going through the basics of setting up a web3 development environment with Rust, NodeJS and the Solana SDK.
We start by downloading the latest Solana release from Github. If you’re on a Mac like me, get solana-release-x86_64-apple-darwin.tar.bz2 and set it up:
tar jxf solana-release-x86_64-apple-darwin.tar.bz2
cd solana-release/
export PATH=$PWD/bin:$PATH
Generate key and save the output in a safe place
solana-keygen new -o ./validator-keypair.json
Wrote new keypair to ./validator-keypair.json
pubkey: 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY
Save this seed phrase and your BIP39 passphrase to recover your new keypair:
# ...
it prints a pubkey which enables us to airdrop ourselves play tokens on the devnet:
solana airdrop 1 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY --url https://api.devnet.solana.com
Requesting airdrop of 1 SOL
Signature: 5deUJ1tWRZb3oNoAsJ9NSfb7F2EEMPgTKYxFEL1FGSeNYktHh2PDU46J7958x1MV4aWSFWXB5VRaryNw1CCmVPd1
1 SOL
we can find the transaction signature on the Solana explorer 5deUJ1tWRZb3oNoAsJ9NSfb7F2EEMPgTKYxFEL1FGSeNYktHh2PDU46J7958x1MV4aWSFWXB5VRaryNw1CCmVPd1
check the balance to confirm the airdrop was successful
solana balance 8RJ66oyximASYmucMXL5LDx6kSbqUgfesuDEm2wmXCwY --url https://api.devnet.solana.com
1 SOL
solana transfer --from <KEYPAIR> <RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT> --fee-payer <KEYPAIR>
We create a second wallet and transfer funds to it
solana-keygen new --no-passphrase --no-outfile
Generating a new keypair
pubkey: 62PFC8bjfti96Aig3sY41o8t5n8v91c7gDS252JJMJZg
Save this seed phrase to recover your new keypair:
# ...
solana transfer --from ./validator-keypair.json 62PFC8bjfti96Aig3sY41o8t5n8v91c7gDS252JJMJZg 0.1 --allow-unfunded-recipient --url https://api.devnet.solana.com --fee-payer ./validator-keypair.json
Signature: 2qJoBsVfB2d7XaovdcuNCaxTVhRAoJvcgrA6bAHRDhKqGxS9YbPYY3GLiqyHGgYcG71ryGQPk2DwLGFaCtCgDMu5
I create notes on the technical details on how Solana smart contracts work under the hood in a separate article. Here we will stick to pragmatic examples and how to deploy and execute on-chain programs.
We’re installing rust via rustup. This installs the Rust Programming Language from the official release channels and enables us to switch between stable, beta, and nightly compilers and keep them updated.
brew install rustup-init
rustup-init
Programs are constrained to run deterministically, so random numbers are not available.
For logging, Rust’s println!
macro is computationally expensive and not
supported. Instead the helper macro msg!
is provided.
My environment at this point looks like this:
rustc --version
rustc 1.57.0 (f1edd0429 2021-11-29)
solana --version
solana-cli 1.8.5 (src:76c5c94a; feat:52865992)
node --version
v15.8.0
Set CLI config url to localhost cluster
solana config set --url localhost
Config File: /Users/noxim/.config/solana/cli/config.yml
RPC URL: http://localhost:8899
WebSocket URL: ws://localhost:8900/ (computed)
Keypair Path: /Users/noxim/.config/solana/id.json
Start a local Solana cluster:
solana-test-validator
Notice! No wallet available. `solana airdrop` localnet SOL after creating one
Ledger location: test-ledger
Log: test-ledger/validator.log
Identity: 5JjzuqdS9B36QxTxokq8QA2MNxP5GZRehEsfY9q8JJTW
Genesis Hash: 2tW5hFVULXwyznwZRPMzPyUPPjWvochGb9trQ2xLFwVU
Version: 1.8.5
Shred Version: 16631
Gossip Address: 127.0.0.1:1024
TPU Address: 127.0.0.1:1027
JSON RPC URL: http://127.0.0.1:8899
⠦ 00:00:14 | Processed Slot: 27 | Confirmed Slot: 27 | Finalized Slot: 0 | Snaps
Ctrl+C will not delete the data on this validator, should something go wrong
with this validator or should you desire to reset your localnet blockchain,
start it with solana-test-validator --reset
.
Grab the hello world example from Solana labs and compile the rust program
git clone https://github.com/solana-labs/example-helloworld
cd example-helloworld
npm run build:program-rust
create a default signer and deploy the on-chain program, note that this will fail initially if we don’t airdrop ourselves funds. I’ve included this example as I’ve seen it commonly encountered online with local clusters.
solana-keygen new -o /Users/noxim/.config/solana/id.json
solana program deploy dist/program/helloworld.so
Error: Account 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1 has insufficient funds for spend (0.4223676 SOL) + fee (0.00032 SOL)
solana aidrop 1
Requesting airdrop of 1 SOL
Signature: 4X5JbjNooagQ4Q73NvPKPP1EMuURmjsGLmNh81EGA1g14e97TMsaYxKKRfpdvuM5yfAYBgKTz4HrccU3u9JJa81z
solana program deploy dist/program/helloworld.so
Program Id: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
We can inspect the file type and see we got a Executable and Linkable Format (ELF), a common standard for executables and shared objects, in the eBPF instruction set.
We can inspect the program with the Solana toolchain as follows:
solana program show 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
Program Id: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: 6hskN36DBFFx4GRE4Ux1sDr5mzt2UhHe8gUfAhigZDsy
Authority: 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1
Last Deployed In Slot: 17583
Data Length: 121024 (0x1d8c0) bytes
Balance: 0.84353112 SOL
An account in Solana has an owner. This owner refers to the program that owns and can make changes to the data in this account (more on storage later). We can inspect the account of the program with:
solana account 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
Public Key: 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
Balance: 0.00114144 SOL
Owner: BPFLoaderUpgradeab1e11111111111111111111111
Executable: true
Rent Epoch: 0
Length: 36 (0x24) bytes
0000: 02 00 00 00 54 c3 0c 2a a7 b2 bd 4b 84 49 d6 b0 ....T..*...K.I..
0010: da 9b 10 98 27 ba 62 d8 09 4c 79 06 75 8c 60 d1 ....'.b..Ly.u.`.
0020: 25 e2 82 cc %...
file dist/program/helloworld.so: ELF 64-bit LSB shared object, eBPF, version 1 (SYSV), dynamically linked, stripped
run the web3 Javascript client
npm install
npm run start
Let's say hello to a Solana account...
Connection to cluster established: http://localhost:8899 { 'feature-set': 52865992, 'solana-core': '1.8.5' }
Using account 8jz3nUqvoxBwxeNSJtcPEnu1X65gaVF9Gab5qecuGET1 containing 0.15500744 SOL to pay for fees
Using program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM
Creating account 8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW to say hello to
Saying hello to 8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW
8qz4R9s3WSWsuFZikiGSnbG19vfkF8qPqM2RGFVZVjmW has been greeted 1 time(s)
Success
Running solana logs
in a second window will show us the transaction on another run
Transaction executed in slot 2566:
Signature: 4n6VL8i5FjUgefe3bZXNiuZJGs87D19iByxnAseimNGATRdYUeDKFbs4hPSHLwH6hHSWGZNbgPNZtSpMU1E7Hj3k
Status: Ok
Log Messages:
Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM invoke [1]
Program log: Hello World Rust program entrypoint
Program log: Greeted 2 time(s)!
Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM consumed 1174 of 200000 compute units
Program 2YStkyjmtn6mpTfH8xE5q74wGtPKwhXm9RvuVrue36zM success
The web3 app does these five things:
async function main() {
console.log("Let's say hello to a Solana account...");
// Establish connection to the cluster
await establishConnection();
// Determine who pays for the fees
await establishPayer();
// Check if the program has been deployed
await checkProgram();
// Say hello to an account
await sayHello();
// Find out how many times that account has been greeted
await reportGreetings();
console.log('Success');
}
Don’t forget to point your Solana CLI again to the devnet if we want to move development to staging:
solana config set --url devnet
Anchor is a development framework that will save us a ton of time and takes away some low level details with macros. See the anchor introductory guide for a more in-depth tutorial. The source code can be found on project-serum/anchor.
Install anchor and yarn which anchor depends on;
npm install -g yarn
npm i -g @project-serum/anchor-cli
if you’re on MacOS or any of their unsupported operating systems, build from source
cargo install --git https://github.com/project-serum/anchor --tag v0.19.0 anchor-cli --locked
anchor init <new programm>
to run anchor build
I had to set the following
export ANCHOR_WALLET=~/.config/solana/id.json
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
The first time we build this program with anchor build
, anchor will generate a key pair
for it. This pair will be stored in the /target
directory (target/deploy/<my program>-keypair.json
). The pubkey will become the unique identifier of your
program, the program ID. Note that we need to update our program ID. It will
be displayed on deployment, but we can also access it with the Solana tools:
solana address -k target/deploy/myapp-keypair.json
8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv
Update Anchor.toml
[programs.localnet]
my_solana_app = "8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv"
and lib.rs
declare_id!("8Xcscw4KgUtHGVqCRNDKFRXMo2jPPs974VSj9JH9eYQv");
anchor deploy
takes the BPF and deploys it with a transaction on the selected
cluster. The command anchor test
takes care of all steps including spinning up
a local ledger that terminates after execution. If we do run a local test
validator already and want to test closer to what we’d get on dev and main net,
we run the steps anchor test --skip-local-validator
which does:
anchor build
anchor deploy
anchor test
This may fail with
tsconfig.json" needs an import assertion of type "json"
You will need to install ts-mocha
:
yarn add ts-mocha
The last step executes the “test” script from the Anchor.toml
file. You can
also invoke it individually with anchor run test
. You’re very likely to
require packages such as spl-token that you can install with:
yarn add @solana/spl-token
To continue the journey and go into more details, I’ve since added another article on Solana On-Chain Programs.
You’ll find plenty of directives in generated and popular anchor programs such
as #[account(mut)]
for writable accounts, #[account(zero)]
to assert program
accounts, #[account(signer)]
to assert that the given account signed the
transaction and many others as well as combinations of the above.
See Derive Macro anchor_lang::Accounts to quickly know what’s what.
To deploy with the solana
command to the same program ID, specify the keypair
in the deploy command to deploy to a specific program id:
solana program deploy --program-id <KEYPAIR_FILEPATH> <PROGRAM_FILEPATH>
solana program deploy --program-id project-name-keypair.json project-name.so
Under certain scenarios during testing we want to deploy a second version of our on-chain program without destroying or overwriting the first version. For example when we have a web client and want to test against two different versions of the same code base. I started to rotate anchor program IDs for this as follows:
mv target/deploy/<project-name>-keypair.json target/deploy/<project-name>-keypair.json.6uaD9SSV12fLxiENKzzqUvZ7t8k2QzLxnpbLXqzKwKeH
anchor build
anchor deploy
assuming 6uaD9SSV12fLxiENKzzqUvZ7t8k2QzLxnpbLXqzKwKeH
was my previous program
ID for quick references. We then need a new keypair and plug the program ID into
a few places:
Running anchor test --skip-local-validator
now should work again.
I couldn’t deploy to my test validator one day and got this error:
Error: Account allocation failed: RPC response error -32002: Transaction simulation failed: Error processing Instruction 1: Unsupported program id [2 log messages]
Resetting the validator with solana-test-validator -r
helped and resolved this issue.
A lot of built errors I found clean out when reinstalling/rebuilding the local
toolchain and sometimes just removing the old Solana SDK via rm -rf ~/.cache/solana
. It’s the early days.
Ensure you have set the anchor wallet environment variable.
export ANCHOR_WALLET=~/.config/solana.id.json