
RustでSolanaのオンチェーンプログラムをデプロイする
はじめに
2020年夏頃からDeFi(分散型金融)が流行し、2021年に入ってNFT(非代替性トークン)が注目され、ブロックチェーン市場全体が盛り上がりを見せています。2021年4月時点では、米国の人口の約14%が仮想通貨を保有しているという調査結果もあります。
よくスタートアップ界隈で「キャズム」という言葉が使われます。キャズムとは、初期市場からメインストリーム市場に成長するまでの間には、市場に製品やサービスが普及するための深い溝があるという理論です。
キャズム理論では、市場におけるユーザー層は、製品の普及率に合わせて5つ(イノベーター、アーリーアダプター、アーリーマジョリティ、レイトマジョリティ、ラガード)に分類されます。イノベーターとアーリーアダプターが初期市場に分類されますが、両者を合わせて約16%とされています。
DeFiを始めとした金融はあくまでインフラなので、これからNFTのような様々なDapps(ブロックチェーンを使ったアプリケーション)が登場し、ブロックチェーンがキャズムを超えて成長するか今後の動向が楽しみです。
前置きが長くなりましたが、2021年に注目を集めるブロックチェーンの一つがSolanaです。2020年に流行したDeFiは、Ethereumというブロックチェーンを使っているのですが、イーサリアムは取引するための手数料が高く、処理速度も遅いという問題があります。solanaは秒間5万以上のトランザクション性能があり、手数料も安いブロックチェーンです。
https://blog-cml.com/pest/defi-trends/
これから、SolanaでDappsを開発する流れをnoteに記載したいと思います。まず今回は、簡単なアプリケーションをSolana上にデプロイするまでの流れです。
SolanaとRustの環境構築
このnoteでは、Solanaでの開発にRustを使用するため、Dockerを使ってRustとSolanaの開発環境を構築します。また、このnoteでは、Dockerを使ったことがある前程で記載します。Docker環境の構築については詳細な解説はしませんので、予めご了承ください。
まずは、デスクトップに好きな名前でフォルダを作り、以下のようにファイルを作成します。
.
├── Dockerfile
├── docker-compose.yml
└── docker-sync.yml
次に、それぞれのファイルに以下を記載します。
Dockerfile
FROM rust:latest
WORKDIR /usr/src/app
COPY . .
RUN cd /usr/src/app
# solana開発のために必要
RUN sh -c "$(curl -sSfL https://release.solana.com/v1.6.6/install)"
# Rustの環境構築
RUN sh -c "$(rustup component add rls rust-analysis rust-src)"
RUN sh -c "$(cargo install cargo-edit)"
# solanaのPATHを通す
RUN sh -c "$(export PATH='/root/.local/share/solana/install/active_release/bin:$PATH':$PATH)"
docker-compose.yml
version: '3'
volumes:
solana-rust-dapp-sync:
external: true
services:
app:
build: .
volumes:
- solana-rust-dapp-sync:/usr/src/app
ports:
- "7878:7878"
tty: true
docker-sync.yml
version: '2'
syncs:
solana-rust-dapp-sync:
src: '.'
ここまで作成したら、ターミナルで以下のコマンドを実行します。
$ docker volume create --name=solana-rust-dapp-sync
$ docker-sync start
$ docker-compose up -d --build
少し時間がかかりますが、Docker環境構築に成功したら、以下のコマンドを実行してDocker内に入ります。
$ docker-compose exec app /bin/bash
なお、補足ですが、Solana公式がDocker Hubにコンテナイメージを公開しているのでこちらを使っても構いません。
Dockerに入ったら、Docker内で下記のコマンドを実行します。
$ solana config set --url https://devnet.solana.com
これは、solanaに接続する際に使用するネットワークを設定するコマンドです。今回はdevnetに接続します。
Devnetは、ユーザー、トークンホルダー、アプリ開発者、または検証者として、Solanaを試乗したい人のための遊び場として機能します。
アプリケーション開発者は、Devnetをターゲットにする必要があります。
もしここで、エラーが発生して `solana` コマンドが通らなかったら、下記のコマンドを実行してみてください。
$ export PATH='/root/.local/share/solana/install/active_release/bin:$PATH':$PATH
ここまでできたら、環境構築は終了です。
プロジェクトを作成する
環境構築ができたので、簡単なアプリケーションを作りたいと思います。まずは、新しいプロジェクトを作成します。Rustで新しいプロジェクトを作成するには、下記のコマンドを実行します。
$ cargo new sample --bin
今回は、 `sample` という名前のプロジェクトにしましたが、名前は何でも構いません。すると、以下のようなフォルダ構成になったかと思います。
.
├── Dockerfile
├── docker-compose.yml
├── docker-sync.yml
└── sample
├── Cargo.toml
└── src
└── main.rs
次に、sampleディレクトリ直下に、Xargo.tomlを作成し、以下の内容を記載します。
Xargo.toml
[target.bpfel-unknown-unknown.dependencies.std]
features = []
Solana開発時には、必ず `[target.bpfel-unknown-unknown.dependencies.std]` が必要になります。
また、Cargo.tomlファイルを以下の内容に修正します。
Cargo.toml
[package]
name = "sample"
version = "0.1.0"
authors = []
edition = "2018"
[dependencies]
solana-program = "1.4.8"
[dev-dependencies]
solana-sdk = "1.4.8"
[lib]
crate-type = ["cdylib", "lib"]
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
SolanaをRustで開発するには、少なくとも `solana-program` が必要になります。
さらに、srcフォルダにあるmain.rsを削除して新たにlib.rsファイルとentrypoint.rsファイルを作成します。ファイルの内容は後ほど記載しますので、現時点では空のファイルで問題ありません。
ここまでできたら、プロジェクト作成は一段落です。現時点のディレクトリ構成は以下のような形になります。
.
├── Dockerfile
├── docker-compose.yml
├── docker-sync.yml
└── sample
├── Cargo.toml
├── Xargo.toml
└── src
├── entrypoint.rs
└── lib.rs
プログラムを作成する
それでは、簡単なプログラムを作成しましょう。先程作成したlib.rsファイルを修正します。
lib.rs
#![deny(missing_docs)]
mod entrypoint;
pub use solana_program;
`mod entrypoint;` という行で、 entrypointという名前のファイルがentorypointという名前のモジュールとして扱われます。
また、 `pub use solana_program;` について、Solanaの開発では、少なくとも `solana_program` というクレートが必要になります。クレートとは、Rubyで言うgemみたいなものです。
次に、同じく先程作成したentrypoint.rsファイルを修正します。
entrypoint.rs
use solana_program::{
account_info::AccountInfo,
entrypoint,
entrypoint::ProgramResult,
pubkey::Pubkey,
msg,
};
// solana-program-sdkの `entrypoint` を呼び出し。
entrypoint!(process_instruction);
fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
msg!("プログラム実行");
Ok(())
}
`msg!("プログラム実行");`という行で、SolanaのTransactionのProgram Logに「プログラム実行」と表示するだけの簡単なプログラムを作成しました。なお、Solanaでは `plintln!` をサポートしていないため、 `msg!` で出力しています。
Solanaのdevnetにデプロイ
とても簡単なプログラムを作成したので、devnetにデプロイします。まず、作成したプロジェクトをcargoのコマンドでビルドします。
まずは、デプロイするためにSolanaのアカウントを作成します。Docker内で下記のコマンドを実行します。
$ solana-keygen new
すると、パスフレーズの入力を求められますので、適当なパスフレーズを入力すれば、`/root/.config/solana/` ディレクトリに `id.json` というファイルが作成されます。
また、下記のコマンドでpubkeyを確認することができます。
$ solana-keygen pubkey /root/.config/solana/id.json
CLh9qcaQzBUHBN8wPVpqNqnnuSfwDR3tjGBePAFE9cm2
次に、デプロイのために必要なSOLをAirdropします。devnetのSOLなので、本物のSOLではありません。開発用にAirdropすることができます。ここでは、下記のコマンドで10SOLをAirdropします。
$ solana airdrop 10 CLh9qcaQzBUHBN8wPVpqNqnnuSfwDR3tjGBePAFE9cm2
上記のコマンドは、ご自身のpubkeyに置き換えて実行してください。成功したら、下記コマンドで残高を確認することができます。このコマンドも同様に、ご自身のpubkeyに置き換えて実行してください。
$ solana balance CLh9qcaQzBUHBN8wPVpqNqnnuSfwDR3tjGBePAFE9cm2
10 SOL
また、下記のサイトでネットワークをdevnetに設定し、pubkeyを検索することでも残高を確認できます。
作成したアカウントに残高が入ったのが確認できたら、sampleディレクトリ直下で以下のコマンドを実行し、プロジェクトをビルドします。
$ cargo build-bpf
すると、新たにtargetディレクトリが作成され、下記のようなディレクトリ構成になったかと思います。
.
├── Dockerfile
├── docker-compose.yml
├── docker-sync.yml
└── sample
├── Cargo.lock
├── Cargo.toml
├── Xargo.toml
├── src
│ ├── entrypoint.rs
│ └── lib.rs
└── target
├── CACHEDIR.TAG
├── bpfel-unknown-unknown
├── deploy
└── release
また、コマンドの実行結果にデプロイ用のコマンドが表示されていますので、下記のコマンドを実行して実際にデプロイを行います。
$ solana program deploy /usr/src/app/sample/target/deploy/sample.so
Program Id: CZUgyTgTymhzaswZV2qLAFfMw856KJCb8WL2bqQQ38RB
Program Idが表示されればデプロイ成功です。先程同様に、SolanaのExplorerからプログラムのトランザクションを確認することができます。
また次回以降、フロントエンドから@solana/web3.jsを使用するnoteを書きたいと思いますが、今回デプロイしたプログラムを実行してExplorerで確認すると、TransactionのProgram Logに「プログラム実行」と表示されるはずです。
実行結果はこちら
補足〜今回実行したフロントエンドのコード一部公開〜
フロントエンドから@solana/web3.jsを使って、デプロイしたSolanaのプログラムを実行する方法はまたの機会に記載したいと思いますが、Nuxt.jsを使ってボタンを押したらプログラムが実行される簡単なコードだけ公開しておきます。
Nuxt.jsや@solana/web3.jsの環境設定についてはまたの機会に書きたいと思います。
pages/index.vue
<template>
<div>
<h1>プログラム実行</h1>
<button @click="main">プログラム実行</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue'
import {
TransactionInstruction,
PublicKey,
sendAndConfirmTransaction,
Transaction,
Connection,
Account
} from '@solana/web3.js';
export default Vue.extend({
methods: {
test(connection, account){
const instruction = new TransactionInstruction({
keys: [],
// sampleプログラムのID
programId: new PublicKey('CZUgyTgTymhzaswZV2qLAFfMw856KJCb8WL2bqQQ38RB'),
data: '',
});
console.log("account:", account.publicKey.toBase58())
sendAndConfirmTransaction(
connection,
new Transaction().add(instruction),
[account],
{
skipPreflight: true,
commitment: "singleGossip",
},
).then(()=>{console.log("done")}).catch((e)=>{console.log("error",e)});
},
main() {
// devnetのコネクション作成
let connection = new Connection("https://devnet.solana.com", 'singleGossip');
// プログラム実行するためのアカウント作成
const account = new Account()
const lamports = 10*1000000000
connection.requestAirdrop(account.publicKey, lamports).then(()=>{
console.log("airdrop done")
this.test(connection, account)
});
},
},
})
</script>
まとめ
今回は、SolanaでTransactionのProgram Logにログを出力するだけの簡単なプログラムを作成しました。また次回以降、@solana/web3.jsの使い方等を記載したいと思います。