Solanaアカウントモデル

Solanaでは、すべてのデータは「アカウント」と呼ばれるものに格納されます。Solana上のデータは、単一の「アカウント」テーブルを持つ公開データベースと考えることができ、このテーブルの各エントリが「アカウント」です。すべてのSolanaアカウントは同じ基本的なアカウントタイプを共有しています。

アカウントアカウント

重要ポイント

  • アカウントは最大10MiBのデータを格納でき、実行可能なプログラムコードまたはプログラム状態が含まれます。
  • アカウントは格納されるデータ量に比例したlamport(SOL)でのrentデポジットが必要で、アカウントを閉じると完全に回収できます。
  • すべてのアカウントにはプログラム所有者があります。アカウントを所有するプログラムだけがそのデータを変更したりlamport残高を減らしたりできます。ただし、誰でも残高を増やすことはできます。
  • Sysvarアカウントはネットワーククラスターの状態を格納する特別なアカウントです。
  • program accountはスマートコントラクトの実行可能コードを格納します。
  • データアカウントはプログラムによって作成され、プログラム状態を格納・管理します。

アカウント

Solana上のすべてのアカウントは、32バイトの一意のアドレスを持ち、多くの場合base58エンコードされた文字列として表示されます(例:14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5)。

アカウントとそのアドレスの関係は、キーバリューペアのように機能し、アドレスはアカウントのオンチェーンデータを特定するためのキーとなります。アカウントアドレスは「アカウント」テーブルの各エントリの「一意のID」として機能します。

アカウントアドレスアカウントアドレス

ほとんどのSolanaアカウントは、アドレスとしてEd25519公開鍵を使用しています。

import { generateKeyPairSigner } from "@solana/kit";
// Kit does not enable extractable private keys
const keypairSigner = await generateKeyPairSigner();
console.log(keypairSigner);
Console
Click to execute the code.

公開鍵は一般的にアカウントアドレスとして使用されていますが、SolanaはProgram Derived Addresses(PDA)と呼ばれる機能もサポートしています。PDAは、プログラム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}`);
Console
Click to execute the code.

アカウントタイプ

アカウントの最大サイズは10MiBで、Solana上のすべてのアカウントは同じ基本Accountタイプを共有しています。

アカウントタイプアカウントタイプ

Solana上のすべてのアカウントには以下のフィールドがあります。

Base Account Type
pub struct Account {
/// lamports in the account
pub 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 rent
pub rent_epoch: Epoch,
}

Lamportsフィールド

アカウントの残高はlamport単位で表示されます。lamportはSOLの最小単位です(1 SOL = 10億lamport)。アカウントのSOL残高は、lamportsフィールドの金額をSOLに変換したものです。

Solanaアカウントは、アカウントに保存されているデータ量(バイト単位)に比例した最小限のlamport残高を持つ必要があります。この最小残高は「rent」と呼ばれます。

アカウントに保存されているlamport残高は、アカウントが閉鎖されると完全に回収できます。

データフィールド

アカウントの任意のデータを格納するバイト配列です。データフィールドは一般的に「アカウントデータ」と呼ばれます。

  • program account(スマートコントラクト)の場合、このフィールドには実行可能なプログラムコード自体、または実行可能なプログラムコードを格納する別のアカウントのアドレスが含まれます。
  • 実行不可能なアカウントの場合、通常は読み取り対象の状態が格納されます。

Solanaアカウントからデータを読み取るには、次の2つのステップが必要です:

  1. アドレス(公開鍵)を使用してアカウントを取得する
  2. アカウントのデータフィールドを生のバイトから適切なデータ構造に逆シリアル化する(この構造はアカウントを所有するプログラムによって定義されます)

オーナーフィールド

このアカウントを所有するプログラムのプログラムID(公開鍵)です。

すべてのSolanaアカウントには、所有者として指定されたプログラムがあります。アカウントを所有するプログラムだけが、そのアカウントのデータを変更したり、lamport残高を差し引いたりできます。

プログラムで定義された instructions は、アカウントのデータと lamport 残高をどのように変更できるかを決定します。

実行可能フィールド

このフィールドは、アカウントが実行可能なプログラムであるかどうかを示します。

  • true の場合、アカウントは実行可能なSolanaプログラムです。
  • false の場合、アカウントは状態を保存するデータアカウントです。

実行可能なアカウントでは、owner フィールドにはローダープログラムのプログラムIDが含まれています。ローダープログラムは、実行可能なプログラムアカウントを読み込み、管理する役割を持つ組み込みプログラムです。

Rent Epochフィールド

rent_epoch フィールドは、現在は使用されていないレガシーフィールドです。

元々、このフィールドはアカウントがネットワーク上でデータを維持するために rent(lamportで)を支払う必要がある時期を追跡していました。しかし、このrent徴収メカニズムはその後廃止されました。

Lamportsフィールド

アカウントの残高はlamport単位で表示されます。lamportはSOLの最小単位です(1 SOL = 10億lamport)。アカウントのSOL残高は、lamportsフィールドの金額をSOLに変換したものです。

Solanaアカウントは、アカウントに保存されているデータ量(バイト単位)に比例した最小限のlamport残高を持つ必要があります。この最小残高は「rent」と呼ばれます。

アカウントに保存されているlamport残高は、アカウントが閉鎖されると完全に回収できます。

データフィールド

アカウントの任意のデータを格納するバイト配列です。データフィールドは一般的に「アカウントデータ」と呼ばれます。

  • program account(スマートコントラクト)の場合、このフィールドには実行可能なプログラムコード自体、または実行可能なプログラムコードを格納する別のアカウントのアドレスが含まれます。
  • 実行不可能なアカウントの場合、通常は読み取り対象の状態が格納されます。

Solanaアカウントからデータを読み取るには、次の2つのステップが必要です:

  1. アドレス(公開鍵)を使用してアカウントを取得する
  2. アカウントのデータフィールドを生のバイトから適切なデータ構造に逆シリアル化する(この構造はアカウントを所有するプログラムによって定義されます)

オーナーフィールド

このアカウントを所有するプログラムのプログラムID(公開鍵)です。

すべてのSolanaアカウントには、所有者として指定されたプログラムがあります。アカウントを所有するプログラムだけが、そのアカウントのデータを変更したり、lamport残高を差し引いたりできます。

プログラムで定義された instructions は、アカウントのデータと lamport 残高をどのように変更できるかを決定します。

実行可能フィールド

このフィールドは、アカウントが実行可能なプログラムであるかどうかを示します。

  • true の場合、アカウントは実行可能なSolanaプログラムです。
  • false の場合、アカウントは状態を保存するデータアカウントです。

実行可能なアカウントでは、owner フィールドにはローダープログラムのプログラムIDが含まれています。ローダープログラムは、実行可能なプログラムアカウントを読み込み、管理する役割を持つ組み込みプログラムです。

Rent Epochフィールド

rent_epoch フィールドは、現在は使用されていないレガシーフィールドです。

元々、このフィールドはアカウントがネットワーク上でデータを維持するために rent(lamportで)を支払う必要がある時期を追跡していました。しかし、このrent徴収メカニズムはその後廃止されました。

Base Account Type
pub struct Account {
/// lamports in the account
pub lamports: u64,
}
// Example Token Mint Account
Account {
lamports: 1461600,
}
// Example Token Program Account
Account {
lamports: 4513200894,
}

Rent

オンチェーンでデータを保存するには、アカウントは保存されるデータ量(バイト単位)に比例したlamport(SOL)残高も維持する必要があります。この残高は「rent」と呼ばれますが、アカウントを閉じると全額回収できるため、デポジットのような仕組みになっています。計算方法はこちらで、これらの定数を使用しています。

「rent」という用語は、rent閾値を下回るアカウントから定期的にlamportを差し引く廃止されたメカニズムに由来しています。このメカニズムは現在は有効ではありません。

プログラムオーナー

Solanaでは、「スマートコントラクト」はプログラムと呼ばれています。プログラムの所有権はSolanaアカウントモデルの重要な部分です。すべてのアカウントには、オーナーとして指定されたプログラムがあります。オーナープログラムのみが次のことを行えます:

  • アカウントのdataフィールドを変更する
  • アカウントの残高からlamportを差し引く

各プログラムはアカウントのdataフィールドに保存されるデータの構造を定義します。プログラムのinstructionsによって、このデータとアカウントの lamports残高がどのように変更されるかが決まります。

System Program

デフォルトでは、すべての新しいアカウントは System Programに所有されています。System Programは以下の主要な機能を実行します:

機能説明
新規アカウント作成System Programのみが新しいアカウントを作成できます。
スペース割り当て各アカウントのデータフィールドのバイト容量を設定します。
プログラム所有権の割り当てSystem Programがアカウントを作成した後、指定されたプログラム所有者を別のprogram accountに再割り当てすることができます。これにより、カスタムプログラムがSystem Programによって作成された新しいアカウントの所有権を取得します。
SOL転送System Accountから他のアカウントへlamport(SOL)を転送します。

Solana上のすべての「ウォレット」アカウントはSystem Programが所有する「System Account」であることに注意してください。これらのアカウントのlamport残高は、ウォレットが所有するSOLの量を示しています。System Accountのみがトランザクション手数料を支払うことができます。

System AccountSystem Account

SOLが初めて新しいアドレスに送信されると、そのアドレスにSystem Programが所有するアカウントが自動的に作成されます。

以下の例では、新しいkeypairが生成され、SOLで資金提供されます。コードを実行して出力を確認してください。アカウントのownerフィールドがアドレス11111111111111111111111111111111のSystem Programであることに注意してください。

import {
airdropFactory,
createSolanaRpc,
createSolanaRpcSubscriptions,
generateKeyPairSigner,
lamports
} from "@solana/kit";
// Create a connection to Solana cluster
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate a new keypair
const keypair = await generateKeyPairSigner();
console.log(`Public Key: ${keypair.address}`);
// Funding an address with SOL automatically creates an account
const 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);
Console
Click to execute the code.

Sysvarアカウント

Sysvarアカウントは、クラスターの状態データへのアクセスを提供する、事前に定義されたアドレスにある特別なアカウントです。これらのアカウントは、ネットワーククラスターに関するデータで動的に更新されます。Sysvarアカウントの完全なリストはこちらで確認できます。

次の例は、Sysvar Clockアカウントからデータを取得して逆シリアル化する方法を示しています。

import { createSolanaRpc } from "@solana/kit";
import { fetchSysvarClock, SYSVAR_CLOCK_ADDRESS } from "@solana/sysvars";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const accountInfo = await rpc
.getAccountInfo(SYSVAR_CLOCK_ADDRESS, { encoding: "base64" })
.send();
console.log(accountInfo);
// Automatically fetch and deserialize the account data
const clock = await fetchSysvarClock(rpc);
console.log(clock);
Console
Click to execute the code.

program account

Solanaプログラムをデプロイすると、実行可能なprogram accountが作成されます。program accountはプログラムの実行可能コードを保存します。program accountはローダープログラムによって所有されています。

Program AccountProgram Account

簡単に言えば、program accountをプログラム自体として扱うことができます。プログラムのinstructionsを呼び出す際、program accountのアドレス(一般的に「プログラムID」と呼ばれる)を指定します。

次の例では、Token Programアカウントを取得して、program accountが同じ基本 Account 型を持っていることを示しています。ただし、executable フィールドは true に設定されています。program accountはデータフィールドに実行可能コードを含むため、データを逆シリアル化しません。

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);
Console
Click to execute the code.

Solanaプログラムをデプロイすると、それはprogram accountに保存されます。program accountはローダープログラムによって所有されています。ローダーにはいくつかのバージョンがありますが、loader-v3を除くすべてのバージョンは実行可能コードを直接program accountに保存します。loader-v3は実行可能コードを別の「プログラムデータアカウント」に保存し、program accountはそれを指すだけです。新しいプログラムをデプロイする場合、Solana CLIはデフォルトで最新のローダーバージョンを使用します。

バッファアカウント

Loader-v3には、デプロイメントやアップグレード中にプログラムのアップロードを一時的にステージングするための特別なアカウントタイプがあります。loader-v4でもバッファは存在しますが、それらは単なる通常のプログラムアカウントです。

プログラムデータアカウント

Loader-v3は他のすべてのBPF Loaderプログラムとは異なる動作をします。プログラムアカウントには実行可能コードを格納するプログラムデータアカウントのアドレスのみが含まれています:

プログラムデータアカウントプログラムデータアカウント

これらのプログラムデータアカウントをプログラムのデータアカウント(以下参照)と混同しないでください。

データアカウント

Solanaでは、プログラムの実行可能コードはプログラムの状態とは異なるアカウントに格納されます。これは、オペレーティングシステムが通常プログラムとそのデータを別々のファイルに持つのと似ています。

状態を維持するために、プログラムは所有する別個のアカウントを作成するための指示を定義します。これらの各アカウントには独自のユニークなアドレスがあり、プログラムによって定義された任意のデータを格納できます。

データアカウントデータアカウント

新しいアカウントを作成できるのはSystem Programだけであることに注意してください。System Programがアカウントを作成すると、その後、新しいアカウントの所有権を別のプログラムに割り当てることができます。

つまり、カスタムプログラム用のデータアカウントを作成するには2つのステップが必要です:

  1. System Programを呼び出してアカウントを作成し、所有権をカスタムプログラムに転送する
  2. アカウントを所有するようになったカスタムプログラムを呼び出し、プログラムの指示で定義されたアカウントデータを初期化する

このアカウント作成プロセスは多くの場合、単一のステップとして抽象化されていますが、基礎となるプロセスを理解することは役立ちます。

以下の例は、Token 2022プログラムが所有するトークンMintアカウントを作成して取得する方法を示しています。

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,
fetchMint
} from "@solana-program/token-2022";
// Create Connection, local validator in this example
const rpc = createSolanaRpc("http://localhost:8899");
const rpcSubscriptions = createSolanaRpcSubscriptions("ws://localhost:8900");
// Generate keypairs for fee payer
const feePayer = await generateKeyPairSigner();
// Fund fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: feePayer.address,
lamports: lamports(1_000_000_000n),
commitment: "confirmed"
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Get default mint account size (in bytes), no extensions enabled
const space = BigInt(getMintSize());
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(space).send();
// Instruction to create new account for mint (token 2022 program)
// Invokes the system program
const createAccountInstruction = getCreateAccountInstruction({
payer: feePayer,
newAccount: mint,
lamports: rent,
space,
programAddress: TOKEN_2022_PROGRAM_ADDRESS
});
// Instruction to initialize mint account data
// Invokes the token 2022 program
const initializeMintInstruction = getInitializeMintInstruction({
mint: mint.address,
decimals: 9,
mintAuthority: feePayer.address
});
const instructions = [createAccountInstruction, initializeMintInstruction];
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Create transaction message
const 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 transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(
signedTransaction,
{ commitment: "confirmed" }
);
// Get transaction signature
const 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);
const mintAccount = await fetchMint(rpc, mint.address);
console.log(mintAccount);
Console
Click to execute the code.

Is this page helpful?

目次

ページを編集