Solanaアカウントモデル
Solanaでは、すべてのデータは「アカウント」と呼ばれるものに保存されます。Solana上のデータは、単一の「アカウント」テーブルを持つ公開データベースと考えることができ、このテーブルの各エントリが「アカウント」です。すべてのSolanaアカウントは同じ基本的なアカウントタイプを共有しています。
アカウント
重要ポイント
- アカウントは最大10MiBのデータを保存でき、実行可能なプログラムコードまたはプログラムの状態が含まれます。
- アカウントには保存されるデータ量に比例したlamport(SOL)でのrentデポジットが必要で、アカウントを閉じると完全に回収できます。
- すべてのアカウントにはプログラムownerがあります。アカウントを所有するプログラムだけがそのデータを変更したりlamport残高を減らしたりできます。ただし、残高を増やすことは誰でもできます。
- Sysvarアカウントはネットワーククラスターの状態を保存する特別なアカウントです。
- プログラムアカウントはスマートコントラクトの実行可能コードを保存します。
- データアカウントはプログラムによって作成され、プログラムの状態を保存・管理します。
アカウント
Solana上のすべてのアカウントには、一意の32バイトのアドレスがあり、通常はbase58エンコードされた文字列(例:vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo6Xd7D
)として表示されます。
アカウントとそのアドレスの関係は、キーバリューペアのように機能し、アドレスはアカウントのオンチェーンデータを特定するためのキーとなります。アカウントアドレスは「アカウント」テーブルの各エントリの「一意のID」として機能します。
アカウントアドレス
ほとんどのSolanaアカウントは、アドレスとしてEd25519公開鍵を使用しています。
import { generateKeyPairSigner } from "@solana/kit";// Kit does not enable extractable private keysconst keypairSigner = await generateKeyPairSigner();console.log(keypairSigner);
公開鍵は一般的にアカウントアドレスとして使用されていますが、Solanaはプログラム派生アドレス(PDAs)と呼ばれる機能もサポートしています。PDAsは、プログラムIDとオプションの入力(seed)から決定論的に導出できる特殊なアドレスです。
import { Address, getProgramDerivedAddress } from "@solana/kit";const programAddress = "11111111111111111111111111111111" as Address;const seeds = ["helloWorld"];const [pda, bump] = await getProgramDerivedAddress({programAddress,seeds});console.log(`PDA: ${pda}`);console.log(`Bump: ${bump}`);
アカウントタイプ
アカウントの最大サイズは10MiBで、Solana上のすべてのアカウントは同じ基本Accountタイプを共有しています。
アカウントタイプ
Solana上のすべてのアカウントには以下のフィールドがあります:
data
: アカウントの任意のデータを格納するバイト配列。実行不可能なアカウントの場合、これは読み取り用の状態を格納することが多いです。プログラムアカウント(スマートコントラクト)の場合、実行可能なプログラムコードが含まれています。このデータフィールドは一般的に「アカウントデータ」と呼ばれています。executable
: このフラグはアカウントがプログラムであるかどうかを示します。lamports
: lamportで表されるアカウントの残高。lamportはSOLの最小単位です(1 SOL = 10億lamport)。owner
: このアカウントを所有するプログラムのプログラムID(公開鍵)。所有者プログラムのみがアカウントのデータを変更したりlamport残高を差し引いたりできます。rent_epoch
: Solanaがアカウントから定期的にlamportを差し引く仕組みがあった時代の名残のフィールド。このフィールドはAccountタイプにまだ存在していますが、rentコレクションが廃止されたため、現在は使用されていません。
pub struct Account {/// lamports in the accountpub lamports: u64,/// data held in this account#[cfg_attr(feature = "serde", serde(with = "serde_bytes"))]pub data: Vec<u8>,/// the program that owns this account. If executable, the program that loads this account.pub owner: Pubkey,/// this account's data contains a loaded program (and is now read-only)pub executable: bool,/// the epoch at which this account will next owe rentpub rent_epoch: Epoch,}
import {airdropFactory,createSolanaRpc,createSolanaRpcSubscriptions,generateKeyPairSigner,lamports} from "@solana/kit";// Create a connection to Solana clusterconst rpc = createSolanaRpc("http://localhost:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate a new keypairconst keypair = await generateKeyPairSigner();console.log(`Public Key: ${keypair.address}`);// Funding an address with SOL automatically creates an accountconst signature = await airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: keypair.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});const accountInfo = await rpc.getAccountInfo(keypair.address).send();console.log(accountInfo);
rent
オンチェーンでデータを保存するには、アカウントは保存されるデータ量(バイト単位)に比例したlamport(SOL)残高を維持する必要があります。この残高は「rent」と呼ばれますが、アカウントを閉じると全額回収できるため、実際にはデポジットのように機能します。計算方法はこちらで、これらの定数を使用しています。
「rent」という用語は、rentのしきい値を下回るアカウントから定期的にlamportを差し引いていた廃止されたメカニズムに由来しています。このメカニズムは現在では有効ではありません。
プログラムオーナー
Solanaでは、「スマートコントラクト」はプログラムと呼ばれています。プログラムの所有権はSolanaアカウントモデルの重要な部分です。すべてのアカウントには、オーナーとして指定されたプログラムがあります。オーナープログラムのみが以下のことを行えます:
- アカウントの
data
フィールドを変更する - アカウントの残高からlamportを差し引く
システムプログラム
デフォルトでは、すべての新しいアカウントはシステムプログラムによって所有されています。システムプログラムは以下のような重要な機能を持っています:
- 新規アカウント作成:システムプログラムのみが新しいアカウントを作成できます。
- スペース割り当て:各アカウントのデータフィールドのバイト容量を設定します。
- プログラム所有権の転送/割り当て:システムプログラムがアカウントを作成した後、指定されたプログラムオーナーを別のプログラムアカウントに再割り当てすることができます。これにより、カスタムプログラムがシステムプログラムによって作成された新しいアカウントの所有権を取得できます。
Solana上のすべての「ウォレット」アカウントは、単にシステムプログラムによって所有されているアカウントです。これらのアカウントのlamport残高は、ウォレットが所有するSOLの量を示しています。システムプログラムによって所有されているアカウントのみがトランザクション手数料を支払うことができます。
システムアカウント
Sysvarアカウント
Sysvarアカウントは、クラスターの状態データへのアクセスを提供する、事前に定義されたアドレスにある特別なアカウントです。これらのアカウントは、ネットワーククラスターに関するデータで動的に更新されます。Sysvarアカウントの完全なリストはこちらで確認できます。
import { Address, createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const SYSVAR_CLOCK_ADDRESS ="SysvarC1ock11111111111111111111111111111111" as Address;const accountInfo = await rpc.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" }).send();console.log(accountInfo);
プログラムアカウント
Solanaプログラムをデプロイすると、実行可能なプログラムアカウントが作成されます。プログラムアカウントには、プログラムの実行可能コードが格納されます。
プログラムアカウントはローダープログラムによって所有されています。
プログラムアカウント
簡単に言えば、プログラムアカウントをプログラム自体として扱うことができます。プログラムの命令を呼び出す際には、プログラムアカウントのアドレス(一般的に「プログラムID」と呼ばれる)を指定します。
import { Address, createSolanaRpc } from "@solana/kit";const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");const programId = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as Address;const accountInfo = await rpc.getAccountInfo(programId, { encoding: "base64" }).send();console.log(accountInfo);
Solanaプログラムをデプロイすると、それはプログラムアカウントに格納されます。プログラムアカウントはローダープログラムによって所有されています。ローダーにはいくつかのバージョンがありますが、loader-v3を除くすべてのローダーは実行可能コードを直接プログラムアカウントに格納します。loader-v3は実行可能コードを別の「プログラムデータアカウント」に格納し、プログラムアカウントはそれを指すだけです。新しいプログラムをデプロイする際、Solana CLIはデフォルトで最新のローダーバージョンを使用します。
バッファアカウント
loader-v3には、デプロイ中または再デプロイ/アップグレード中にプログラムのアップロードを一時的にステージングするための特別なアカウントタイプがあります。loader-v4では、バッファは依然として存在しますが、それらは単なる通常のプログラムアカウントです。
プログラムデータアカウント
loader-v3は他のすべてのBPF Loaderプログラムとは異なる動作をします。プログラムアカウントには、実際の実行可能コードを格納するプログラムデータアカウントのアドレスのみが含まれています:
プログラムデータアカウント
これらのプログラムデータアカウントをプログラムのデータアカウント(以下参照)と混同しないでください。
データアカウント
Solanaでは、プログラムの実行可能コードはプログラムの状態とは異なるアカウントに保存されます。これは、オペレーティングシステムが通常、プログラムとそのデータを別々のファイルに持つのと似ています。
状態を維持するために、プログラムは自身が所有する別個のアカウントを作成する命令を定義します。これらのアカウントにはそれぞれ固有のアドレスがあり、プログラムによって定義された任意のデータを保存できます。
データアカウント
新しいアカウントを作成できるのはシステムプログラムだけであることに注意してください。システムプログラムがアカウントを作成すると、新しいアカウントの所有権を別のプログラムに転送または割り当てることができます。
つまり、カスタムプログラム用のデータアカウントの作成には2つのステップがあります:
- システムプログラムを呼び出してアカウントを作成し、所有権をカスタムプログラムに転送する
- アカウントを所有するようになったカスタムプログラムを呼び出し、プログラムの命令で定義されたアカウントデータを初期化する
このアカウント作成プロセスは、しばしば単一のステップとして抽象化されますが、基礎となるプロセスを理解することは役立ちます。
import {airdropFactory,appendTransactionMessageInstructions,createSolanaRpc,createSolanaRpcSubscriptions,createTransactionMessage,generateKeyPairSigner,getSignatureFromTransaction,lamports,pipe,sendAndConfirmTransactionFactory,setTransactionMessageFeePayerSigner,setTransactionMessageLifetimeUsingBlockhash,signTransactionMessageWithSigners} from "@solana/kit";import { getCreateAccountInstruction } from "@solana-program/system";import {getInitializeMintInstruction,getMintSize,TOKEN_2022_PROGRAM_ADDRESS} from "@solana-program/token-2022";// Create Connection, local validator in this exampleconst rpc = createSolanaRpc("http://127.0.0.1:8899");const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");// Generate keypairs for fee payerconst feePayer = await generateKeyPairSigner();// Fund fee payerawait airdropFactory({ rpc, rpcSubscriptions })({recipientAddress: feePayer.address,lamports: lamports(1_000_000_000n),commitment: "confirmed"});// Generate keypair to use as address of mintconst mint = await generateKeyPairSigner();// Get default mint account size (in bytes), no extensions enabledconst space = BigInt(getMintSize());// Get minimum balance for rent exemptionconst rent = await rpc.getMinimumBalanceForRentExemption(space).send();// Instruction to create new account for mint (token 2022 program)// Invokes the system programconst createAccountInstruction = getCreateAccountInstruction({payer: feePayer,newAccount: mint,lamports: rent,space,programAddress: TOKEN_2022_PROGRAM_ADDRESS});// Instruction to initialize mint account data// Invokes the token 2022 programconst initializeMintInstruction = getInitializeMintInstruction({mint: mint.address,decimals: 9,mintAuthority: feePayer.address});const instructions = [createAccountInstruction, initializeMintInstruction];// Get latest blockhash to include in transactionconst { value: latestBlockhash } = await rpc.getLatestBlockhash().send();// Create transaction messageconst transactionMessage = pipe(createTransactionMessage({ version: 0 }), // Create transaction message(tx) => setTransactionMessageFeePayerSigner(feePayer, tx), // Set fee payer(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx), // Set transaction blockhash(tx) => appendTransactionMessageInstructions(instructions, tx) // Append instructions);// Sign transaction message with required signers (fee payer and mint keypair)const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);// Send and confirm transactionawait sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction,{ commitment: "confirmed" });// Get transaction signatureconst transactionSignature = getSignatureFromTransaction(signedTransaction);console.log("Mint Address:", mint.address);console.log("Transaction Signature:", transactionSignature);const accountInfo = await rpc.getAccountInfo(mint.address).send();console.log(accountInfo);
Is this page helpful?