プログラムの理解
このページは、Solanaでプログラムがどのように動作するかの簡単な概要を提供し、特定の主題について詳しく学びたい方のための追加の読み物を提供することを目的としています。
イントロダクション
ほとんどのブロックチェーンとは異なり、Solanaはロジックとデータを2つの別々のコンポーネントに分離しています。これらは、それぞれプログラムとアカウントです。つまり、内部的に変数内にデータを保存する代わりに、プログラムはアカウントに保存された外部データと相互作用し、それらを変更する能力を持ちます。
このアーキテクチャは、プログラムが相互作用するデータがプログラム自体に束縛されず、新たな規模の次元にスケールできるため、プログラムをよりモジュラーにするのに優れています。また、ブロックチェーンが同じプログラムを異なるアカウントで並列実行することを可能にするため、プログラムをより高性能にするのにも優れています。
プログラムと相互作用するには、そのプログラムによって定義されたインストラクションを使用する必要があります。インストラクションは、プログラムによって公開されるAPIエンドポイントと考えることができます。各インストラクションには、実行するために満たす必要のある一連のパラメータと制約が含まれています。
要約すると:Solanaでは、プログラムがアカウントと呼ばれる外部データストアと相互作用するために使用できるインストラクションを定義します。
技術的には、プログラムはexecutableとしてマークされた特別な種類のアカウントであり、その全目的はプログラムのコンパイルされたコードを保存することであることに注意してください。ただし、簡素化のために、これらの定義を区別し、非実行可能アカウントを指すために「アカウント」という用語を使用します。
このガイドの残りの部分では、ドキュメント全体の図で使用される視覚的表現を説明する前に、アカウントとインストラクションについてより詳しく説明します。
📚 追加の読み物:
- Solana Documentation — On-chain Programs
- Solana Cookbook — Programs
- The Anchor Book — Intro to Programming on Solana
アカウント
Solanaでは、アカウントはデータを保存するために使用されます。本質的に、それらは特定のアドレスに保存されたシンプルなバイト配列です。アカウントのアドレスは、暗号鍵ペアの公開鍵です。
その鍵ペアの秘密鍵にアクセスできる人は誰でも、そのアカウントに代わって署名することができ、プログラムによっては、そのアカウントに保存されたデータを変更する能力を与える場合があります。
アカウントが作成されると、通常はプログラムによって即座に初期化され、そのプログラムがオーナーとしてマークされ、割り当てられたバイト配列のデータ構造を定義します。アカウントを所有するプログラムは、それと相互作用するために使用できるインストラクションを提供する責任があります。
📚 追加の読み物:
Program Derived Addresses (PDA)
プログラム派生アカウントと呼ばれる別のタイプのアカウントが存在します。そのアドレスは暗号鍵ペアの公開鍵ではなく、代わりにアカウントを所有するプログラムの公開鍵からアルゴリズムで派生されます。そのアドレスをProgram Derived Addressまたは略してPDAと呼びます。
アドレスは常にプログラムの公開鍵から派生されるため、他のプログラムは同じアドレスをアルゴリズムで派生させることはできません。さらに、アドレスにより多くのコンテキストを追加するために、アルゴリズムに追加のシードを提供することができます。
これは、プログラムがCross-Program Invocationsに署名することを可能にしたり、決定論的に派生できるアドレス内でアカウントの作成を可能にするなど、さまざまな用途があります。
設計上、Program Derived Addressesは暗号的に生成された公開鍵と競合することは決してないことに注意してください。すべての暗号公開鍵は楕円曲線と呼ばれるものの一部です。PDAを生成する際に、アルゴリズムがその曲線上に落ちる鍵を生成した場合、バンプがアドレスに追加され、生成されたアドレスが曲線上に落ちなくなるまで1つずつ増加されます。
📚 追加の読み物:
アカウントデータ
通常のアカウントまたはプログラム派生アカウントのいずれを扱っているかに関係なく、アカウントはデータをシリアル化されたバイト配列として保存します。したがって、プログラムが管理する各アカウントのデータ構造を定義し、これらのアカウントを区別する方法を提供することはプログラムの責任であり、どのデータ構造を適用すべきかがわかります。
ディスクリミネータ
ディスクリミネータは、プログラム内の異なるタイプのアカウントを区別するために使用されます。多くの方法で実装できますが、最も一般的な3つを以下に示します:
- すべてのアカウントの最初のバイトとして共有Enumを使用。すべてのアカウントに共有Enumを接頭辞として付けることで、シリアル化されたデータの最初のバイトを使用してアカウントを識別できます。これはシンプルで効率的なディスクリミネータの実装方法です。Metaplexによって維持されているプログラムのほとんどはこのアプローチを使用します。
- すべてのアカウントの最初のバイトとして決定論的ハッシュを使用。これは前のポイントと非常に似ていますが、Enumの代わりにハッシュを使用します。Anchorフレームワークを使用して作成されたプログラムは、AnchorがアカウントのBezogen名に基づいてそのハッシュを自動的に生成するため、このアプローチを暗黙的に使用することになります。
- ディスクリミネータなし、アカウントのサイズを使用。プログラムが管理するすべてのアカウントが異なるサイズを持つ場合、そのバイト配列の長さを調べて、どのアカウントを扱っているかを判断できます。これは、データに余分なバイトを追加する必要がないため、パフォーマンスが良いアプローチですが、プログラムがアカウントでどれだけ柔軟であるかを制限します。SolanaのSPL Token Programは、2つの異なる固定サイズのアカウントしか維持しないため、このアプローチを使用します。
フィールドタイプ、サイズ、オフセット
各アカウントは、異なるタイプのフィールドを使用して独自のデータ構造を定義します。これらのタイプは、フィールドを保存するために必要なバイト数に影響します。たとえば、i8は1バイトを必要とする8ビット整数であり、i64は8バイトを必要とする64ビット整数です。
ブロックチェーンでは、アカウントは単なるバイト配列なので、各フィールドのサイズとこの配列のどこから開始するか(つまり、そのオフセット)を理解することが重要です。これは、memcmpフィルターを使用して指定されたプログラムから複数のアカウントを取得する際に役立ちます。
すべてのフィールドが固定サイズを持つわけではないことに注意してください。たとえば、Vec<i8>は8ビット整数のベクターであり、なし、1つ、または多くのアイテムを含む場合があります。そのため、最初の可変サイズフィールドの後に位置するフィールドに基づいてアカウントをフィルタリングすることがはるかに複雑になります。
オプションフィールド
フィールドはオプションとして定義することもできます。つまり、そのフィールドが空になるシナリオが存在することを意味します。
このフィールドは、フィールドが空かどうかを示すプレフィックスとして追加のバイトを使用します。
具体的には、値Noneが割り当てられ、プログラムはそのフィールドを使用する際にそれに応じて動作します。
示唆フィールド
これはデータ構造で明示的に定義されているものではありませんが、ドキュメントでは特定のフィールドを示唆的としてマークします。
示唆フィールドは、フィールドによって提供される情報がプログラム自体によって使用されないことを意味します。代わりに、それは第三者に情報の一部を_示唆_します。プログラムは依然としてデータの整合性を強制しますが、その情報を内部的に使用することはありません。
Metadataアカウントを例に取りましょう。
Creators配列の各クリエイターのShareプロパティは示唆的です。Token Metadataプログラムは、すべてのクリエイターのShare値が100%になることを確認しますが、その情報では何もしません。代わりに、NFTマーケットプレイスがロイヤルティを配布する際にこの情報を使用することを期待します。
一方、Is Mutableプロパティは、Token Metadataプログラムがその情報を内部的に使用して不変のMetadata Accountsの更新を防ぐため、示唆的ではありません。
インストラクション
プログラムが提供するインストラクションを使用してプログラムと相互作用することができます。複数のインストラクションを、ブロックチェーンに送信される単一のトランザクションにパックできます。各トランザクションはアトミックであり、そのインストラクションのいずれかが失敗した場合、トランザクション全体がリバートされることを意味します。
アカウントと同様に、インストラクションをネットワークに送信する前にバイト配列にシリアル化する必要があります。シリアル化するデータには、プログラムがそれを実行するための以下の情報を含める必要があります。
- ディスクリミネータ: アカウントと同様に、インストラクションは通常ディスクリミネータが前に付けられるため、プログラムは実行されているインストラクションを識別できます。
- アカウント: このインストラクションによって影響を受けるアカウントアドレスの配列。これは、アカウントが読み取られる、変更される、またはその両方であるためです。プログラムは提供されたアカウントのタイプをその位置に基づいて識別するため、この配列の順序は重要です。
- 引数: インストラクションに必要なデータフィールドの配列。インストラクションがアカウントから直接ほとんどの情報を取得できるため、この配列が空であることは珍しくありません。これらの引数はアカウントのフィールドに匹敵するものであり、したがって「オプション」や「示唆的」などの上記と同じプロパティを持つことができます。
- 署名者: 提供されたアカウントのサブセットの署名配列。これは、インストラクションに署名することが必要なアカウントにのみ必要です。次のセクションでこれについてもう少し詳しく説明します。
📚 追加の読み物:
- Solana Documentation — Transactions
- Solana Documentation — Instructions
- Solana Cookbook — Transactions
署名者および/または書き込み可能アカウント
プログラムは、インストラクション内で提供されるアカウントが署名者および/または書き込み可能であることを要求する場合があります。
- 署名者: 署名者アカウントは、インストラクションが成功するためにトランザクションに署名することが必要です。署名を添付することで、ユーザーはアカウントの所有者であることを証明できます。
- 書き込み可能: 書き込み可能アカウントはインストラクションによって変更されます。この情報は、ブロックチェーンがどのトランザクションを並列実行できるか、どれができないかを知るために重要です。
したがって、これら2つのブール値により、次の4つの可能なシナリオになります:
- 非署名者かつ非書き込み可能: このアカウントはデータを読み取るためにのみ使用されます。変更することはできず、その所有権について仮定することはできません。
- 署名者かつ非書き込み可能: このアカウントも変更することはできませんが、トランザクションを送信したユーザーがその秘密鍵を所有していることがわかります。これにより、プログラムは特定のアクションへのアクセスを許可または拒否できます。
- 署名者かつ書き込み可能: このアカウントはトランザクションに署名_し_、インストラクションによって変更される可能性があります。プログラムは通常、アカウントを変更する前にアカウントの所有者が誰であるかを証明することを要求するため、この組み合わせはかなり一般的です。そうでなければ、誰でも所有していない任意のアカウントを変更できます。
- 非署名者かつ書き込み可能: このアカウントは変更される可能性がありますが、その所有権について仮定することはできません。通常、これはプログラムが他の署名者アカウントを使用してそのアカウントを変更できることを証明していることを意味します。これは、プログラムが所有しており、そのため、プログラムがそれらを変更できる権限を追跡する必要があるPDAアカウントの場合でもあります。また、アカウントにlamportを与信するなどの特定のアクションでは、アカウントがトランザクションに署名する必要がないことに注意してください。
Cross-Program Invocations (CPI)
Cross-Program Invocationsにより、プログラムは自分のインストラクション内で入れ子にされたインストラクションを実行できます。自分のプログラムおよび/または他のプログラムからのインストラクションを使用できます。
📚 追加の読み物:
