🛴

Solanaを理解する上で重要な用語5つを噛み砕いてみる

2022/04/28に公開

✨ Solana の概要


Solana はオープンソースのパブリックチェーンです。
スケーリング問題を解決するブロックチェーンとして 2017 年に開発され、スマートコントラクト・NFT・dApps などに対応しています。

Solana が開発者に愛されるチェーンとなった理由としては、

  • 高速なトランザクションスループット
    • 65,000 TPS(Transaction Per Second) の理論値を持つ
    • 現状は 2,700 TPS(2022/4/23 時点)なので、まだまだスケール可能
  • トランザクションコストがほぼ 0
    ネットワーク 平均トランザクションコスト(2022/4/23 時点)
    Bitcoin $131.69
    Ethereum $2.6
    Solana $0.00025
    • この手数料の安さから『VISA に取って代わる決済システムになる』との期待
  • Rust の人気上昇
    • Solana のProgram(スマートコントラクト)は Rust で実装される
    • Rust は難易度が高く将来が期待できる言語であるため、情報感度の高い熟練したエンジニアを中心とした開発コミュニティを形成できた

このように Solana は将来を期待できるチェーンとして多くの開発者を集めています。

💬 Solana における重要用語

Solana における用語は、Ethereum などで使用される意味とは異なることが多いです。
そんな少しトリッキーながら Solana でよく使用される重要用語を私なりに噛み砕いて説明したいと思います。
(記事内容は GitHub で管理していますので、修正リクエストなどは受け付けています!)

Account

A record in the Solana ledger that either holds data or is an executable program.
Like an account at a traditional bank, a Solana account may hold funds called lamports. Like a file in Linux, it is addressable by a key, often referred to as a public key or pubkey.

(Solana 公式ドキュメントterminologyより引用)

Solana における Account(アカウント)は 2 つに大別できます。

  • Executable Account
    • Program(コントラクトロジック) を実行できるアカウント
    • 変更することができない
  • Non-executable Account
    • いわゆる State を管理するアカウント
    • Owner だけがステートを変更できる
    • Wallet 残高・lamports など、変更可能なデータなどを保存

Executable Account は実行ロジックをバイトコードとして持っているものになります。
(Ethereum の EVM でいう Contract Account に近いイメージですね)
そしてこの Executable Account でのコントラクトを実行できるのは、そのownerのみとなっています。
このアカウントはデプロイ後に中身を変更することができないので、完全に実行ロジックだけに責任を持つのです。

Non-executable Accountはステートを管理することができるアカウントになります。
つまり、スマートコントラクトを実行してウォレット残高を増減させるような場合には Non-executable Account 内のステートを変更する必要があります。
そこで Executable Account が Non-executable Account のオーナーになることによって、そのステートを管理するわけですね。

Solana-Twitter での例


https://lorisleiva.com/ に素晴らしい例があったので引用

こちらは Solana 版 Twitter の図解です。

  1. Solana-Twitter Programという Executable なアカウントがデプロイされる
  2. ↑ はデフォルトでSystem Programという Solana 公式が持つアカウントが Owner となる
  3. 各 Tweet や User は Non-executable なアカウントとしてステートを持つ
  4. Owner である Executable Account によってTweetUserのステートが変更されていく
    アカウント名 種類
    Solana-Twitter Program Executable Account
    Tweet Non-executable Account
    User Non-executable Account

つまり Solana チェーンでは、すべての要素がアカウントとして存在するのです。
これは他のブロックチェーンと異なった思想でしょう。

ソースコード

https://github.com/solana-labs/solana/blob/master/sdk/src/account.rs#L22-L34

ソースコードを見てみましょう。
Account 構造体のフィールドをざっくり説明すると、

フィールド 説明
lamports SOL の最小単位(ETH でいう wei)がいくらストアされているか
data アカウントに含まれるデータ
owner アカウントのオーナーの公開鍵
executable 実行可能かどうか
rent_epoch アカウントが Rent(家賃)を払う期限

補足としては、

  • Solana の通貨で使用される最小単位はlamportといい、1lamport = 0.000000001 SOL
  • 各アカウントはRentという家賃を払う必要があるため、lamports や rent_epoch というフィールドがある

Rentに関しては後に説明するため、ひとまず Account についてはこのくらい分かれば十分でしょう。

Program

The code that interprets instructions.

(Solana 公式ドキュメントterminologyより引用)

Program はコントラクトのロジックを表します。
(いわゆるスマートコントラクトですね)

先に『Solana では全てがアカウントとして存在する』と言いましたが、Program もアカウントの一種です。
Account のうち、executableフィールドが true になっているものが Program として認識されます。

  • Account の作成
  • SOL を転送する
  • ステーキングする
  • Vote をする

といった動作が Program によって実現されているというわけですね!

Anchor での例

Anchor は Solana における Program とそのクライアントを開発するフレームワークの例です。

use anchor_lang::prelude::*;

declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[program]
mod basic_0 {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize {}

チュートリアルで示されている例を見てみると、#[program]のアトリビュートがついているものが Program だと分かります。
basic_0という Program の中身に具体的な実装(RPC request handler)であるinitializeメソッドを確認できます。

クライアント側からの呼び出し

// Read the generated IDL.
const idl = JSON.parse(
  require("fs").readFileSync("./target/idl/basic_0.json", "utf8")
);

// Address of the deployed program.
const programId = new anchor.web3.PublicKey("<YOUR-PROGRAM-ID>");

// Generate the program client from IDL.
const program = new anchor.Program(idl, programId);

// Execute the RPC.
await program.rpc.initialize();

無事に Program がデプロイされるとクライアントからinitializeメソッドを呼び出すことができるわけですね。

Instruction

The smallest contiguous unit of execution logic in a program.
An instruction specifies which program it is calling, which accounts it wants to read or modify, and additional data that serves as auxiliary input to the program.
A client can include one or multiple instructions in a transaction. An instruction may contain one or more cross-program invocations.

(Solana 公式ドキュメントterminologyより引用)

Instruction(命令) は Program に含まれる実行ロジックの最小単位です。

Anchor での例

#[program]
mod basic_1 {
    use super::*;

    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }

    pub fn update(ctx: Context<Update>, data: u64) -> Result<()> {
        let my_account = &mut ctx.accounts.my_account;
        my_account.data = data;
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = user, space = 8 + 8)]
    pub my_account: Account<'info, MyAccount>,
    #[account(mut)]
    pub user: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub my_account: Account<'info, MyAccount>,
}

#[account]
pub struct MyAccount {
    pub data: u64,
}

こちらの例でいうinitializeupdateは Instruction になります。
Program の中に実装されているため、Instrution はどの Program が呼び出されているのかを内部から知ることができます。

そしてこれら Instruction が実行されると Account が読み込まれてステートが変更されます。
その証拠にInititalizeUpdateといった Account が定義され、引数のコンテキストに渡されていますよね。

クライアントからの呼び出し

// The program to execute.
const program = anchor.workspace.Basic1;

// The Account to create.
const myAccount = anchor.web3.Keypair.generate();

// Create the new account and initialize it with the program.
await program.rpc.initialize(new anchor.BN(1234), {
  accounts: {
    myAccount: myAccount.publicKey,
    user: provider.wallet.publicKey,
    systemProgram: SystemProgram.programId,
  },
  signers: [myAccount],
});

そしてクライアントからも呼び出すことができます。
RPC request handler としてinitializeメソッドが呼ばれていますよね。

ソースコード

https://github.com/solana-labs/solana/blob/master/sdk/program/src/instruction.rs#L324-L334

ソースコードの Instrution 構造体を見てみましょう。
各フィールドを見てみると、

フィールド 説明
program_id Program の公開鍵。どの Program なら正しくdataを解釈できるかを示す
accounts 参照される Account を並べたメタデータ
data Program に渡されるデータ。実行すべき操作などを含む(特定の Program だけが中身を解釈できる)

つまりこれらをまとめると、

  • Instrution は Program 内の実行ロジックの単位
  • program_idが渡されるので、どの Program にデータを渡すべきか判別できる
  • コントラクトの実行により、ステートを管理する Account を操作することができる
  • クライアントから RPC で実行可能

というわけです。かなり全体像が理解できてきましたよね!

Transaction

One or more instructions signed by a client using one or more keypairs and executed atomically with only two possible outcomes: success or failure.

(Solana 公式ドキュメントterminologyより引用)

Transaction はコントラクトの全体像を示す用語です。
今までの用語を理解していれば、Transaction の流れがスッキリ分かりますよね。


Transaction flow throughout the network Solana WhitePaperより引用

  1. クライアントが Transaction のリクエストをし、Solana のランタイムへ送られる
  2. Solana のランタイムによって Instruction が呼び出される
  3. Instruction はどの Program や Account が関与すべきかを判別し、データを適切な Program へ渡す
  4. Program は Instruction から受け取ったデータを読み込んでコントラクトを実行する
  5. Program はコントラクトの結果がsuccesserrorかを返す
  6. コントラクトに関わった Account のOwner(所有者)は変更を承認するために署名を行う
  7. 検証ノードが Transaction を承認する

ざっくりこのような流れで Transaction が行われます。

ソースコード

https://github.com/solana-labs/solana/blob/master/sdk/src/transaction/mod.rs#L173-L190

Transaction 構造体を見てみましょう。
各フィールドを見てみると、

フィールド 説明
signatures Account が Transaction の結果を承認したことを伝える署名
message Transaction の全体情報をコンパクトにまとめたもの

このようにシンプルな構成になっています。

このmessageフィールドを詳しく見るとMessageHeader構造体が確認できると思いますが、そのなかのnum_required_signaturesというフィールドに示された数字とsignaturesの署名数は一致しなければなりません。

Rent

Since validators on the network need to maintain a working copy of this state in memory, the network charges a time-and-space based fee for this resource consumption, also known as Rent.

(Solana 公式ドキュメントRentより引用)

Solana チェーンに Account を保持するためにはRentと呼ばれるコストがかかります。
Rent は名前の通り"家賃"であり、Solana 上で開発するには避けられない経費です。

なぜ Rent が存在するのか?

Rent が無いとどんな問題が起こりうるでしょうか?

  • 将来起こりうる Transaction に備えて、ずっと Account をメモリスペースに保持しておかないといけない
    • メモリスペースを確保し続けるのもコストがかかるよね...。
    • ほとんど動いていない休眠 Account を残しておくのは無駄じゃない?
  • 悪い開発者が Account を大量に作成して、メモリスペースを独占する可能性
    • 運用に多大なコストをかけられてしまうかも...。

こうした問題に対応するために Rent というシステムが導入されました。

主となる目的は Solana ネットワークを構成するハードウェアの費用を賄うことです。
Account を存在させ続ける限りメモリスペースのコストがかかりますから、維持費としての Rent を払ってもらいます。
また、使わなくなった Account を削除するインセンティブにもなりますからリソースの無駄遣いも防げますよね。

Rent があることで悪質な開発者が Account を大量に作ることが防がれます。
Account を作るごとにコストがかかってしまいますから、ハッカーが少数派である限りネットワークが健全に保たれやすいです。

Rent が徴収されるタイミング

  1. トランザクションで Account が参照されたとき
  2. Epoch(Solana ネットワークで決まっている特定の周期)ごとに 1 回

通常はこの 2 つのタイミングに Rent が徴収されてしまいます。

しかし、2 年分の Rent を先に預けることで徴収が免除される(rent-exempt)ので、Account を作る際には rent-exempt 状態にするのが通常です。
(現状では新しく Account を作る際には rent-exempt にする必要があるようです)

まとめ

Solana で使用される用語は Bitcoin や Ethereum などで使用されるものと異なる部分が多いです。
(僕も最初に Solana で dApps を作った際にはアーキテクチャを理解するのに苦労しました)

理解する難易度が高い上に日本語のドキュメントや記事も少ないですから、僕の説明によって少しでも Solana 開発のイメージが深まれば嬉しいです。

GitHubで編集を提案

Discussion

ログインするとコメントできます