T6 · Solana Account Model
Solana のプログラムが自分でデータを保存しない理由は?
- 練習する能力
- BuilderProtocol Literacy
- 実践
- Devnet でアカウントを作成してデータを書き込み、CLI で owner、lamports、data を読み取る。
- AI Lab
- AI に EVM と Solana のアカウントモデルを比較させ、Solana が並列実行できる理由を説明させる。
現実の問題
複数チェーンの資産ダッシュボードを作っています。EVM 側は完成しました。T5 どおり、トークンコントラクトを見つけ、balanceOf マッピングのアドレスに対応するスロットを計算して読めば残高です。データはコントラクト内にあり、アドレスで索引します。
次は Solana です。同じ考えでトークンプログラムのアドレスを探し、全員の残高の表を探します。
**見つかりません。**プログラムアドレスには残高データが一バイトもなく、コードだけです。
ユーザーのウォレットを見ると、さらに奇妙です。見知らぬアドレスが七、八個結び付き、それぞれ一種類のトークンだけを持ちます。ユーザーは作った覚えもなく、知りません。
製品要件も困ります。新規ユーザーへトークンを送ると失敗します。相手には保管する場所がなく、先に費用を払って作る必要があります。
残高はどこにあり、未知のアドレスは誰が作り、なぜ送金前に何かを作るのでしょう。
思考実験
T5 の世界共通コンピューターを分解します。
T5 は一つの表で、key がアドレス、value にコードとデータが入るモデルです。両者は結び付いています。
これを二つに分けます。
コードを読み取り専用の場所へ移します。 配備されたプログラムは読めても書けず、専用の保存領域もありません。 純粋関数です。
**データは独立したファイルに分かれます。**各「ファイル」がアドレスと次の内容を持ちます。
lamports ファイルにある金額
owner 内容の変更権限を持つプログラム
data 生バイト列。解釈は owner のプログラムが決める
executable 実行可能なコードかどうかowner は資金の所有者ではなく、このバイト列を変更できるプログラムです。ウォレットの署名でトークンアカウントの数字を直接変えられず、トークンプログラムだけが変えます。署名はその変更の許可です。
ここから考えます。
第一段階:保存領域のないプログラムは、どのファイルを変えるかどう知るのでしょう。呼び出し側が指定するしかありません。取引には触れる全ファイルのアドレスを列挙します。
第二段階:各取引が対象ファイルと読み書きを先に列挙すれば、書き込むファイルが重ならない二取引を同時に実行できます。
秘密はそれだけです。並列性は速いマシンではなく、事前宣言の強制で得ます。
第三段階:ファイルのアドレスはどこから来るのでしょう。各ユーザーのポイントをランダムなアドレスに置いたら、次回どう見つけますか。プログラムとユーザーから同じアドレスを決定的に導く必要があります。さらに秘密鍵を持ってはいけません。誰かが鍵でプログラムを迂回して変更できるからです。
三段階をつなぐと冒頭の答えになります。残高は独立ファイル、未知のアドレスは決定的な導出、送金前に作るのはトークンの保管ファイルです。保存領域を使うので誰かが払います。
あなたが決める
プログラムが各ユーザーのポイントを記憶します。どこに保存しますか。
結果を観察する
四案は同じ問題、データをどうアドレス指定するかに答えます。
| EVM(T5) | Solana | |
|---|---|---|
| 保存場所 | コントラクトの storage | 独立したアカウント |
| ユーザーの索引 | key とスロット番号を連結してハッシュ | シードとプログラムアドレスから導出 |
| 変更者 | コントラクトのコード | owner が指すプログラム |
| 保存費用 | 呼び出し側が一度 Gas を払う | 作成時の預託金。閉鎖時に回収可能 |
| 並列性 | 実行後でないとアクセス先が分からず不可 | 読み書き集合を事前宣言するため可能 |
ハッシュによる指定は非常によく似ています。key と名前空間を連結してハッシュします。
分かれるのは最後の二行です。
**一、課金モデル。**EVM は一回払って永久保存し、返金しません。Solana はサイズに応じて預託し、閉鎖時に回収します。状態の膨張へ明示的に価格を付けます。
**二、並列性はスケジューラーが事前に競合を知れるかで決まります。**EVM は知れず直列、Solana は強制宣言でグループ化できます。
アカウントの列挙、作成、アドレス導出という扱いにくさは、すべてこの並列性を得るためです。
モデルを作る
アカウントの全フィールド:
address 32 バイト。公開鍵そのもので、EVM のような追加ハッシュはしない
lamports 残高。1 SOL = 10 の 9 乗 lamports
owner data を変更できるプログラム
data 生バイト列。形式は owner が定義
executable true ならプログラム三つの推論は、それぞれ実際のバグに対応します。
- **ウォレットとトークンアカウントは別物です。**前者はシステムプログラムが owner でネイティブ通貨、後者はトークンプログラムが owner で一種類のトークンを持ちます。画面で統合してもコードでは分けます。
- data の解釈は owner に依存します。プログラムは受け取るアカウントの owner が自分か確認する必要があります。省けば攻撃者が正規レイアウトに見える偽アカウントを渡せます。最もよくある脆弱性の種類です。
- **executable アカウントは読み取り専用です。**プログラムは自分を変更せず、専用の書き込み領域もありません。
取引の構造:
Transaction
├─ signatures[] 署名リスト
└─ message
├─ accounts[] 全対象アカウント。署名者と書き込み可否を指定
├─ recent_blockhash 有効期限の証明。期限切れで無効(T3 の nonce との差)
└─ instructions[] 一つ以上の命令
├─ program_id 呼ぶプログラム
├─ accounts[] 全体リストのどのアカウントを使うか
└─ data 引数のバイト列accounts 配列が核心です。 最適化のヒントではありません。 強制で合意の一部です。列挙しなかったアカウントには実行時に触れません。
並列実行の仕組み:
- 各取引が読み書き集合を宣言
- スケジューラーが競合でグループ化
- 重ならないグループを同時実行
- 重なるものは待機
秘密鍵のない導出アドレス:
複数のシードのバイト列、プログラムアドレス、固定文字列を連結してハッシュします。
結果は約半分の確率で楕円曲線上にあり、秘密鍵に対応してしまうため安全ではありません。追加の一バイトを 255 から減らし、最初に曲線外へ落ちる値を探します。
曲線外なら数学的に対応する秘密鍵がありません。誰も鍵で署名できず、導いたプログラムだけが実行中に代理署名できます。
これがコードによる所有権の技術的意味です。
保存用預託金:
バイト数に応じた最低額を保存しないと回収対象になります。作成前に金額を照会でき、閉鎖時には指定した受取先へ返します。
Gas とは別物です。計算料金でなく保存領域の預託です。取引手数料は書く量と強く結び付きませんが、アカウントのサイズによる預託額は必須です。
何と呼ぶか
唯一の保存単位で、アドレスと lamports、owner、data、executable を持ちます。
すべてがアカウントです。ウォレット、残高、プログラム、設定も該当します。T5 より広い概念で、EVM のアドレスに対し、ここではファイルに近いものです。
executable が true の、コードを持つアカウントです。専用の書き込み可能な保存領域はありません。入力はアカウント群と引数、出力はその変更という純粋関数です。
自分でデータを保存しないのは、設計上その場所がないためです。
data を変更する権限のあるプログラムを指すフィールドです。
資金の所有者ではありません。トークンアカウントの owner はトークンプログラムで、資金を使える人は data の別フィールドに記録されます。おそらく最もよく混同される二つの概念です。
シードとプログラムから決定的に導き、楕円曲線外なので秘密鍵を持たないアドレスです。
索引表なしのデータ指定と、誰も鍵で奪えない資産保有を同時に解決します。
T5 のマッピングの指定に対応しますが、スロット番号と違い、PDA は他プログラムが直接参照できる第一級のアドレスです。
プログラム、対象アカウント、引数を指定する一回の呼び出しです。
複数命令は全部成功するか全部巻き戻ります。アカウント作成、初期化、送金を原子的にまとめられます。EVM ではコントラクトで包む必要があります。
サイズごとに必要な最低 lamports です。満たせば回収されず、閉鎖時に戻ります。
名前は賃料でも挙動は預託です。状態の占有に明示的に価格を付けます。T5 の保存は買い切りで返金されません。
実践
**すべて Devnet で、テスト通貨に価値はなく実資産は使いません。**メインネットの鍵ファイルを使わず、専用の新しいものを作ります。
Devnet へ接続し、ウォレットと通貨を用意します。
solana config set --url https://api.devnet.solana.com
solana-keygen new --outfile ~/solana-lab.json
solana config set --keypair ~/solana-lab.json
solana address
solana airdrop 2
solana balance頻度制限で取得できなければ、時間やフォーセットを変えます。
自分のアカウントを見ます。
solana account $(solana address)lamports、owner、data、executable がそろいます。
owner は自分でなくシステムプログラムです。最初の誤解を直します。owner は資金の所有者でなく、バイト列の変更権限者です。
プログラムアカウントと比較します。
solana account <任意のプログラムアドレス>executable は true、data は長いコンパイル済みコード、lamports は保存用預託額です。
前の結果と並べます。同じ構造でまったく違う役割を持つ、すべてがアカウントという実例です。
預託額を調べます。
solana rent 0
solana rent 165
solana rent 10000サイズに比例して増えます。規模感を覚えます。ユーザー用アカウントの費用と、誰が払うかという製品上の問題を決めます。
データを持つアカウントを作ります。
spl-token create-token
spl-token create-account <前の出力の MINT アドレス>
spl-token mint <MINT アドレス> 100
spl-token accounts最初は通貨の定義である mint、次は自分のトークンアカウントを作ります。冒頭の未知のアドレスがこれで、ウォレットと mint から導出します。
三番目で数字を書きます。変更するのは自分でなくトークンプログラムで、署名は許可です。
生バイト列を読みます。
solana account <create-account が出力したアカウントアドレス>三点を見ます。
ownerは自分でなくトークンプログラムです。署名で許可しても、直接バイト列を変えられません。dataは固定長の生バイト列で、mint、保有者、数量などを順に符号化します。lamportsはそのサイズの預託額です。
**Lab の核心です。**残高はプログラム内でなく、それを owner とする独立アカウント内にあります。
バックエンドと同じ JSON-RPC で読みます。
curl -s https://api.devnet.solana.com -X POST \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo",
"params":["<そのトークンアカウントアドレス>",{"encoding":"base64"}]}'
curl -s https://api.devnet.solana.com -X POST \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["<自分のウォレットアドレス>"]}'getAccountInfo の data は base64 で、復号すると生バイト列です。残高として解釈できるかはレイアウトの知識次第で、生バイト列という設計の結果です。
getBalance は SOL でなく lamports です。最もよくある単位ミスで、10 の 9 乗違います。
失敗する送金で冒頭の問題を体験します。
solana-keygen new --outfile ~/solana-lab-2.json --no-bip39-passphrase
spl-token transfer <MINT アドレス> 1 <二つ目のウォレットアドレス>相手にトークンアカウントがないと通知されます。spl-token transfer --help で作成オプションを調べて再実行すれば成功し、自分の残高から預託金が引かれます。
**新規ユーザーへの送金には、先に保管場所を作る費用が必要だと分かります。**決済、エアドロップ、出金の設計時に、誰が払うか決めます。
AI Lab
三段階で行い、第三で問題が見つかりやすくなります。
第一段階:
EVM と Solana のモデルを表で比較してください。
保存場所、ユーザーデータの指定、変更権限、保存料金、並列性を含めます。
各行で事実だけでなく、その理由を説明してください。
第二段階:
Solana が並列で EVM が直列な理由、並列の前提、直列へ落ちる条件を示してください。
第三段階:
二取引の具体例を、並列可能な組と不可能な組で示してください。
全アカウントを列挙し、読み取り専用か書き込み可能か記してください。第三が検証点です。前二段階はきれいに答えても、具体的なリストでは誤りが出ます。読み書きの逆転や必須プログラムアカウントの欠落がよくあります。
Lab の solana account の実フィールドと、列挙されたものを照合します。
高価な誤りを探す専用の問いもあります。
プログラムがアカウント引数を受け取っても owner を検証しない場合、
攻撃者は何ができますか。完全な攻撃手順を示してください。最もよくある脆弱性の種類で、「実例」で結果を扱います。説明は正しくても、モデルが書くコードは検証を漏らしがちです。**説明と実装は別物で、人にもモデルにも当てはまります。**T10 で全体を学びます。
AI の回答後、自分で必ず検証すること
- プログラムのコードとデータを同じアカウントと説明していない
- PDA の鍵をプログラムが保管すると説明していない。鍵自体がない
- owner を資金の所有者と混同しない。data の変更権限者である
- 読み取り同士は並列、書き込みが競合すれば直列という条件を確認する
- CLI サブコマンド、引数、RPC 名を公式文書で確認できる
- lamports と SOL の 10 の 9 乗の差を正しく変換する
- ブロックに入ったことと命令の成功を同一視しない。失敗状態もある
- Rent を時間課金でなく返還可能な預託と説明する
実例
あるステーブルコインのプログラムが、引数の一つの正当性検証を漏らしました。
攻撃者は自分で管理する、正規のバイト配置に見える偽アカウントを渡しました。プログラムは受け入れて大量発行し、資金を失いました。
原因はdata が生バイト列で、正しいアカウントかは owner とアドレス導出の検証でしか確かめられないことです。
EVM では自分の storage に他人のデータを挿し込めず、この種類は少なめです。安全性の優劣でなく、攻撃面の違いです。
高人気の発行で、全取引が同じカウンターへ書きました。
書き込み集合が重なり、グループ化も並列化もできません。ブロックハッシュの期限切れが多発し、再試行が混雑を悪化させました。
高並列を目指すなら、全ユーザーが同じアカウントへ書くかを先に問います。そうならカウンターを分割し、ユーザー別にするか、オフチェーン集約してバッチで書きます。
T5 と比較すると、EVM の同じスロットへの書き込みは高価なだけですが、ここでは中心的な利点を失います。
数万アドレスへのエアドロップをループで送ったチームが、多数の失敗に遭いました。
多くはその通貨を持ったことがなく、対応アカウントがありませんでした。命令は宛先を見つけられず巻き戻りました。
一取引で作成してから送るのが正解です。複数命令は原子的です。預託金の支払者も決めます。配布者なら数万件分の実費、ユーザーなら無償配布ではなくなります。
Solana 決済のチームがほぼ一度は踏む問題です。EVM では任意アドレスへのトークン送金に事前準備が不要なためです。
作成時のサイズが決まっているため、後でユーザーデータへフィールドを追加したら既存アカウントに入りませんでした。
各ユーザーに拡張と預託差額の支払いを求める移行、または新旧配置の長期共存が必要でした。どちらも困難です。
**配備時に構造を固定する代償です。**予約バイトを残すのは基本習慣で、地味でも苦しい移行を一回避けられます。
変数を一つ変える
競合を判断できず、取引を順番に実行するしかありません。
**T5 のモデルになります。**設計者が並列性を思い付かなかったのでなく、変更先が実行後でないと分からない性質が、事前の調整を妨げます。
逆も同じです。EVM のアクセスリストは任意で課金だけに影響します。強制してスケジューラーにつなげれば、この章のモデルです。
全ユーザーを一つの巨大アカウントへ入れ、オフセットで指定するでしょう。
サイズ上限で追加アカウントが必要になり、全書き込みが重なって直列になり、一人の変更でも全体を writable にして他を止めます。
移行者が最もよく犯す構成上の誤りです。テスト環境では分からず、ユーザーが増えてから表れます。
T5 の買い切りへ戻ります。短期差は小さくても長期では、不要アカウントを清掃する動機がなく状態は増える一方です。
返還可能なら閉鎖して回収する動機があります。状態膨張を制限でなく価格で解決する、経済による技術問題の解決です。
直列です。スケジューラーはアカウント単位の読み書きだけを見て、data のどのバイトかは見ません。
並列性の粒度はフィールドでなくアカウントです。どれだけ細かく分けたかで決まります。
データ構造の設計が性能設計そのものです。EVM より強く当てはまり、T10 でも繰り返し使います。
持ち帰る問い
T5 と合わせ、データの場所と変更者を曖昧な直感から描ける構造へ変えます。二モデルは違って見えても、指定方法、書き込み権限、保存料金の支払者という三問に答えます。それが分かれば第三のモデルも読めます。
新規ユーザーへの送金にはアカウント作成が必要で、自社かユーザーが払います。避けられない製品上の問いです。多くの初回利用フローの形は、この預託金が決めています。
AI Agent の権限境界は明確でも危険です。対象を列挙するので理論上は署名前に一件ずつ確認できます。ただし鍵で勝手に構築すれば確認点が消えます。アカウントリスト審査を強制することは自然に使える安全策で、T33 で扱います。
章末チェック
並列性のためです。
競合を事前に知るには対象の宣言が必要で、その宣言は外から指定できる独立アカウントにデータがある時に意味を持ちます。
内部に隠れれば指定も競合判定もできません。コードとデータの分離は並列性の構造的前提です。
key と名前空間をハッシュする指定方法は似ますが、二点違います。
一、PDA は第一級のアドレスで、他プログラムから参照でき、資産を持ち、取引リストにも載ります。mapping は内部スロットです。
二、PDA に秘密鍵はなく、導出したプログラムだけが代理署名できます。プログラムによる保有を数学的に保証します。
**data を変更できるプログラムです。**資金の所有者ではありません。
ウォレットならシステムプログラム、トークンならトークンプログラムです。使える人は data の別フィールドです。
混同は脆弱性に直結します。owner を確かめないプログラムには偽アカウントを渡せます。
二取引の書き込み可能なアカウントが重なれば、直列です。
全体カウンター、単一プール、人気発行の状態など、全員が同じ場所へ書く設計が該当します。
粒度はアカウントでフィールドではありません。違うバイトを変えても直列で、分割の細かさが並列度を決めます。
唯一の正解はありません。次を確認します。
- 分離の理由は見た目でなく並列性だと説明する。
- owner と資金の所有者を区別する。
- PDA は指定と所有権、mapping は指定だけだと説明する。
- 返還可能な預託と買い切り Gas、状態膨張の関係を説明する。
- アカウント単位の並列性と構造設計の性能への影響を示す。
- 一つの巨大アカウントへの移行という誤りを警告する。
- 指定、書き込み権限、保存料金という共通の三問を示す。
7 まで説明できれば API の暗記でなく、共通の枠組みでどのチェーンも読めます。それがこの段階の目標です。
要点
Solana はコードとデータを完全に分離し、トランザクションは触れるアカウントを事前に宣言しなければならない。