Solana Development Tutorial: Things you should know before structuring your code(和訳)
著:Solong
ETHやEOSなど、他のチェーンでのプログラミング経験がある方。Solanaでプログラミングを始めると、期待していたものとは違った動作をして戸惑うことがあります。この記事では、Solanaエコシステムの初期の開発者として、これらの混乱を解消し、オンボーディング体験をよりスムーズにするためのいくつかの提案をしたいと思います。
アカウントのサイズは固定されています
アカウントは、EOSのRAMのように、Solanaにデータを保存するために使われます。しかし、一度アカウントのサイズを決めてしまうと、後からサイズを変更することはできません。Solana SDKに慣れている方は、この機能を見たことがあると思います。
pub fn allocate(pubkey: &Pubkey, space: u64) -> Instruction {
let account_metas = vec![AccountMeta::new(*pubkey, true)];
Instruction::new(
system_program::id(),
&SystemInstruction::Allocate { space },
account_metas,
)
}
将来の計画では、Solanaはサイズを変更する機能を追加するかもしれません。ここでは、私たちの議論の一部を見ることができます。
ランタイムでのみAccountInfoを取得
AccountInfoの構造は以下の通りです。
pub struct AccountInfo<'a> {
/// Public key of the account
pub key: &'a Pubkey,
/// Was the transaction signed by this account's public key?
pub is_signer: bool,
/// Is the account writable?
pub is_writable: bool,
/// The lamports in the account. Modifiable by programs.
pub lamports: Rc<RefCell<&'a mut u64>>,
/// The data held in this account. Modifiable by programs.
pub data: Rc<RefCell<&'a mut [u8]>>,
/// Program that owns this account
pub owner: &'a 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,
}
この構造体は、チェーン上のあらゆる種類のアカウントを格納することができ、SOLアカウント、SPLアカウント、プログラムアカウントなどを表すことができます。
通常は、"HSfwVfB7RUF1SKCd4yrz8KZp7TU262Y5BeZZN1tdCTVk"のようなアカウントアドレスがあれば、以下のような機能が使えると思うかもしれません。
AccountInfo::new
AccountInfo::From
アカウント情報を取得してプログラムで使用することは、残念ながらできません。今のところ、アカウント情報を取得する唯一の方法は、クライアントからアドレスを取得し、アカウント情報を読み込んでフィードバックするSolanaノードのランタイムにあります。
例えば、あるプログラムアカウントがSOLを他のアカウントに転送したい場合、Tokenプログラムの転送機能を使用します。
・src_info: source account info
・dst_info: destination account info
・system_program_info: system program account info
プログラムではオンチェーンでアカウントを取得することはできず、PRCを通じて顧客から取得する必要があります。
私たちのプログラムのアカウントに保存されているアカウントを選択して、いくつかのSOLを送信するというようなことはできないわけです。
まずオンチェーンでアカウントを選択し、その結果を得て、このアカウントのAccountInfoをクライアント経由で送信し、それを使ってSOLを送信する必要があります。
プログラムアカウントは、SOLを直接転送することはできません。
簡単な宝くじプログラムを作りたいとします。すべてのプレイヤーが1SOLをプログラムに転送し、プログラムが勝者を選び、すべてのSOLを転送する、というものですが、必要なアカウント数は?
普通に考えれば、"受信"、"選択"、"送信"がオールインワンになっている1アカウントで十分だと思われるかもしれません。
まず、SOLを理解する必要があります。SOLはトークンであり、すべてのSOLアカウントの所有者はシステムプログラム - "1111111111111111111111111"です。
続いて、プログラムアカウントを見てみましょう。
プログラムアカウントの所有者は、BPFLoader2 - "BPFLoader2111111111111111111111 "であり、最大の違いは、転送コードを見ると、プログラムアカウントの"実行可能"プロパティが"Yes"になっていることです。
// The balance of read-only and executable accounts may not change
if self.lamports != post.lamports {
if !self.is_writable {
return Err(InstructionError::ReadonlyLamportChange);
}
if self.is_executable {
return Err(InstructionError::ExecutableLamportChange);
}
}
アカウントが実行可能であれば、それを実現することはできません。
そこで、別のSOLアカウントを使って、すべてのSOLを受信し、最後に勝者に送信して、以前のケースのように勝者がフィードインするようにします。
演算とログの限界
Solanaのプログラムをデバッグしていると、以下のようなエラーが出ることがあります。
Program GJqD99MTrSmQLN753x5ynkHdVGPrRGp35WqNnkXL3j1C consumed 200000 of 200000 compute units
Program GJqD99MTrSmQLN753x5ynkHdVGPrRGp35WqNnkXL3j1C BPF VM error: exceeded maximum number of instructions allowed (193200)
Program GJqD99MTrSmQLN753x5ynkHdVGPrRGp35WqNnkXL3j1C failed: custom program error: 0xb9f0002
これは、計算リソースが不足していることを意味します。ランタイムはプログラムにいくつかの制限を加えており、SDK:で確認できます。
BpfComputeBudget {
max_units: 100_000,
log_units: 0,
log_64_units: 0,
create_program_address_units: 0,
invoke_units: 0,
max_invoke_depth: 1,
sha256_base_cost: 85,
sha256_byte_cost: 1,
max_call_depth: 20,
stack_frame_size: 4_096,
log_pubkey_units: 0,
}; if feature_set.is_active(&bpf_compute_budget_balancing::id()) {
bpf_compute_budget = BpfComputeBudget {
max_units: 200_000,
log_units: 100,
log_64_units: 100,
create_program_address_units: 1500,
invoke_units: 1000,
..bpf_compute_budget
};
}
現在、メインネット、テストネットともに、演算ユニットの上限は20万台、ログの上限は100台となっています。
以下を御覧ください。
Transaction simulation failed: Error processing Instruction 0: Program failed to complete
Program N42Qjxtrb1KMwCshrpbSJxj3khrZwN51VVv5Zdv2AFL invoke [1]
Program log: [solong-lottery]:Instruction: SignIn
Program log: [solong-lottery]:process_signin lottery:award[0] fund[1000000000] price[1000000000] billboard[Epj4jWrXq4JsEAhvDKMAdR47GqZve8dyKp29KdGfR4X] pool[255]
Program log: Error: memory allocation failed, out of memory
Program N42Qjxtrb1KMwCshrpbSJxj3khrZwN51VVv5Zdv2AFL consumed 109954 of 200000 compute units
Program N42Qjxtrb1KMwCshrpbSJxj3khrZwN51VVv5Zdv2AFL BPF VM error: BPF program Panicked in at 0:0
Program failed to complete: UserError(SyscallError(Panic("", 0, 0)))
Program N42Qjxtrb1KMwCshrpbSJxj3khrZwN51VVv5Zdv2AFL failed: Program failed to c
これは、メモリ不足を意味します。スタックのスペースリミットは4kb、ヒープは32kbです。VecやBoxのような構造体を使うときは、本当に注意しなければなりません。ブロックチェーンの世界では、メモリの制限に常に注意を払う必要があります。
追記:再帰的な関数の呼び出しに注意し、depthは20を超えないようにしてください
ELF error
また、プログラムを公開する際にも、このようなエラーが出ることがあります。
Error: dynamic program error: ELF error: ELF error: .bss section not supported
または、
Failed to parse ELF file: read-write: base offet 207896
これは、ハッシュマップやfind_program_addressのような関数のように、SolanaがサポートしていないRustの構造/機能を使用している可能性があることを意味しています。
ここでは、いくつかのルールをご紹介します。
以下にはアクセスできません。
・rand or any crates that depend on it
・std::fs
・std::net
・std::os
・std::future
・std::process
・std::sync
・std::task
・std::thread
・std::time
以下はアクセスが限定的に制限されています
・std::hash
・std::os
・Bincodeはサイクル数とcall depthの両方で非常に計算量が多く、避けるべきです。
・文字列のフォーマットは、計算コストもかかるので避けた方がいいでしょう。
・"println!"," print!"をサポートしていません。src/log.rsのSolana SDKヘルパーを代わりに使用してください。
・ランタイムは、1つの命令を処理する間にプログラムが実行できる命令の数に制限をかけます。
いつでも公式ドキュメントを確認することが出来ます。
プログラムアップデート
最終的にプログラムをデプロイした後、いくつかの機能を変更したいのですが、アカウントやアドレスを変更せずにプログラムをアップデートできますか?
今のところ答えはNOですが、公式チームはこれに取り組んでいます。
とりあえず、いくつかのヒントがあります。
1.プログラムのアドレスをフロントエンドにハードコードしないで、再デプロイに備えて常にインターフェイスを介してプログラムのアドレスを取得してください。
2.アカウントのコピー機能を作成しておけば、データを失いたくないがアップデートが必要になったときに、このコピー機能を使って古いアカウントから新しいアカウントにデータを移行することができます。
結論
Solanaのユニークなデザインと効率性を追求した結果、プログラムのビルドや実行が他のチェーンと異なることがあり、最初は奇妙に感じるかもしれませんが、その哲学を理解すれば慣れて効率性を楽しむことができます。この記事がより良いオンボーディング体験の助けになることを願っています。
この記事が気に入ったらサポートをしてみませんか?