Solana開發(fā)學(xué)習(xí)筆記(一)——從Hello World出發(fā)
筆者注:因近期筆者工作需要,開始接觸 Solana 鏈上程序開發(fā)。本系列文章是筆者的學(xué)習(xí)筆記,既是為了備忘,也是希望得到 Solana 開發(fā)者的指點(diǎn)與交流。本系列文章將默認(rèn)讀者已經(jīng)掌握 Rust 的基礎(chǔ)語法,故不涉及對(duì) Rust 語法細(xì)節(jié)的解釋。如果讀者對(duì) Rust 基礎(chǔ)語法還不熟練的話,本文下方推薦的 Rust 入門書籍《Rust 編程入門、實(shí)戰(zhàn)與進(jìn)階》學(xué)習(xí)。
1.1 Solana 簡(jiǎn)介
Solana 是一個(gè)高性能、無許可的底層公鏈,專注于在不犧牲去中心化或安全性的前提下提供可擴(kuò)展性。Solana 主網(wǎng)于 2020 年一季度上線,目前主網(wǎng)的全球節(jié)點(diǎn)超過 800 個(gè),TPS 最高可達(dá) 6.5 萬,出塊時(shí)間約 400 毫秒。
Solana 的共識(shí)算法采用 PoH(歷史證明),其核心是一個(gè)去中心化時(shí)鐘,該時(shí)鐘旨在解決缺乏單個(gè)可信賴時(shí)間源在分布式網(wǎng)絡(luò)中的時(shí)間問題。PoH 免除了在節(jié)點(diǎn)網(wǎng)絡(luò)中廣播時(shí)間戳的需求,從而提高整個(gè)網(wǎng)絡(luò)的效率。
1.1.1 鏈上程序
Solana 的智能合約叫做鏈上程序(On-chain Program),Solana 官方提供了 Rust 和 C 的 SDK 來支持開發(fā)鏈上程序。鏈上程序的開發(fā)工作流如圖 1-1 所示,開發(fā)者可以使用工具將程序編譯成 Berkley Packet Filter (BPF) 字節(jié)碼(文件以 .so 為擴(kuò)展名),再部署到 Solana 鏈上,通過 Sealevel 并行智能合約運(yùn)行時(shí)去執(zhí)行智能合約的邏輯。此外,基于 Solana JSON RPC API,官方提供了諸多 SDK 用于客戶端與 Solana 鏈上數(shù)據(jù)交互。
圖 1-1 鏈上程序開發(fā)工作流
1.1.2 賬戶模型
與以太坊類似,Solana 也是基于賬戶模型的區(qū)塊鏈。通過將任意狀態(tài)存儲(chǔ)于鏈上賬戶并同步復(fù)制給集群中的所有節(jié)點(diǎn),可以創(chuàng)建復(fù)雜而強(qiáng)大的去中心化應(yīng)用程序。
Solana 提供了一套不同于以太坊的賬戶模型,賬戶定義的字段如表 1-1 所示。Solana 的賬戶可以分為可執(zhí)行賬戶和不可執(zhí)行賬戶。
- 可執(zhí)行賬戶:存儲(chǔ)不可變的數(shù)據(jù),主要用于存儲(chǔ)程序的 BPF 字節(jié)碼。
- 不可執(zhí)行賬戶:存儲(chǔ)可變的數(shù)據(jù),主要用于存儲(chǔ)程序的狀態(tài)。
表 1-1 賬戶定義字段
字段 | 描述 |
---|---|
lamports | 賬戶余額 |
owner | 賬戶所有者 |
executable | 是否為可執(zhí)行賬戶 |
data | 賬戶存儲(chǔ)的數(shù)據(jù) |
rent_epoch | Solana鏈上程序的部署是按其賬戶大小進(jìn)行定期收費(fèi)的,如果賬戶無法支付租金,系統(tǒng)將清除該賬號(hào) |
我們知道以太坊上每個(gè)智能合約的代碼和狀態(tài)都存儲(chǔ)在同一個(gè)賬戶中,而 Solana 鏈上程序是只讀或無狀態(tài)的,即程序的賬戶(可執(zhí)行賬戶)只存儲(chǔ) BPF 字節(jié)碼,不存儲(chǔ)任何狀態(tài),程序會(huì)把狀態(tài)存儲(chǔ)在其他獨(dú)立的賬戶(不可執(zhí)行賬戶)中。為了區(qū)分某個(gè)賬戶是用作哪個(gè)程序的狀態(tài)存儲(chǔ),每個(gè)賬戶都指定了一個(gè)程序作為其所有者。程序可以讀取其不作為所有者的賬戶中的狀態(tài),但只有作為所有者的程序才能修改賬戶中的狀態(tài),任何其他程序所做的修改都會(huì)被還原并導(dǎo)致交易失敗。
更多關(guān)于賬戶模型的資料可以參見官方文檔:https://solana.wiki/zh-cn/docs/account-model/
1.2 搭建編程環(huán)境
在開始 Solana 鏈上程序開發(fā)之前,需要先安裝和配置相關(guān)的編程環(huán)境。首先請(qǐng)正確安裝 Node、NPM 和 Rust 的最新穩(wěn)定版本,下面來安裝 Solana CLI 并配置相關(guān)環(huán)境。
1.2.1 安裝 Solana CLI
Solana CLI 是與 Solana 集群進(jìn)行交互的命令行管理工具,包含節(jié)點(diǎn)程序 solana-validator、密鑰對(duì)生成工具 solana-keygen,以及合約開發(fā)工具 cargo-build-bpf、cargo-test-bpf 等。
在終端運(yùn)行以下命令,可完成 Solana CLI 最新穩(wěn)定版的下載與安裝。
sh -c "$(curl -sSfL https://release.solana.com/stable/install)"
如果安裝成功,會(huì)出現(xiàn)以下內(nèi)容。
downloading stable installer ? stable commit e9bef425 initialized Adding export PATH="~/.local/share/solana/install/active_release/bin:$PATH" to ~/.profile Adding export PATH="~/.local/share/solana/install/active_release/bin:$PATH" to ~/.bash_profile Close and reopen your terminal to apply the PATH changes or run the following in your existing shell: export PATH="~/.local/share/solana/install/active_release/bin:$PATH"
Solana CLI 的所有命令行工具都安裝在 ~/.local/share/solana/install/active_release/bin 中,并會(huì)自動(dòng)將該路徑加入 ~/.profile 和 ~/.bash_profile 文件的 PATH 環(huán)境變量。
運(yùn)行以下命令,檢查 PATH 環(huán)境變量是否已正確設(shè)置。
solana --version // solana-cli 1.7.18 (src:e9bef425; feat:140464022)
如果能顯示 solana-cli 的版本號(hào)、版本哈希等信息,代表環(huán)境變量設(shè)置成功。如果未看到這些信息,請(qǐng)檢查相關(guān)文件中 PATH 環(huán)境變量設(shè)置的路徑是否正確。
如果已安裝過 Solana CLI,想升級(jí)到最新版本,可在終端運(yùn)行以下命令。
solana-install update
1.2.2 配置 Solana CLI
1. 連接到集群
Solana 的集群有本地集群(localhost)和公開集群。根據(jù)不同的用途,公開集群又分為開發(fā)者網(wǎng)絡(luò)(devnet)、測(cè)試網(wǎng)(testnet)和主網(wǎng)(mainnet-beta)。
- devnet 是適用于開發(fā)者的集群,開發(fā)者可獲得 SOL token 的空投,但這個(gè) SOL token 不具有真實(shí)價(jià)值,僅限測(cè)試使用。devnet 的 RPC 鏈接是 https://api.devnet.solana.com 。
- testnet 是用于測(cè)試最新功能的集群,如網(wǎng)絡(luò)性能、穩(wěn)定性和驗(yàn)證程序行為等。同樣可獲得 SOL token 的空投,但也僅限測(cè)試使用。testnet 的 RPC 鏈接是 https://api.testnet.solana.com 。
- mainnet-beta 是主網(wǎng)集群,在 Mainnet Beta 上發(fā)行的 SOL token 具有真實(shí)價(jià)值。mainnet-beta 的 RPC 鏈接是 https://api.mainnet-beta.solana.com 。
運(yùn)行以下命令,根據(jù)實(shí)際需要來選擇集群。
// 選擇localhost集群 solana config set --url localhost // 選擇devnet集群 solana config set --url devnet
2. 創(chuàng)建賬戶
如果是第一次使用 Solana CLI,需要先創(chuàng)建一個(gè)賬戶。運(yùn)行以下命令,根據(jù)操作提示可以設(shè)置一個(gè) BIP39 規(guī)范的密碼,此密碼用來增強(qiáng)助記詞的安全性,當(dāng)然也可以為空。生成新的賬戶后,密鑰對(duì)會(huì)被自動(dòng)寫入 ~/.config/solana/id.json 文件中。需要注意的是,這種存儲(chǔ)密鑰對(duì)的方式是不安全的,僅限開發(fā)測(cè)試使用。
solana-keygen new
要查看當(dāng)前這個(gè)賬戶的公鑰,運(yùn)行以下命令。
solana-keygen pubkey
當(dāng)前如果是在 devnet 集群,該賬戶的余額為 0 SOL,可以運(yùn)行以下命令查詢余額。
solana balance
在 devnet 上申請(qǐng) SOL 空投,運(yùn)行以下命令后再次查詢當(dāng)前賬戶的余額,會(huì)發(fā)現(xiàn)余額為 2 SOL。
solana airdrop 2
1.3 第一個(gè) Solana 項(xiàng)目——Hello World
Hello World 是一個(gè)官方演示項(xiàng)目,展示了如何使用 Rust 和 C 開發(fā)鏈上程序,并使用 Solana CLI 來構(gòu)建與部署,以及使用 Solana JavaScript SDK 與鏈上程序進(jìn)行交互。
1.3.1 Hello World 源碼解讀
example-helloworld 項(xiàng)目的目錄結(jié)構(gòu)如下所示,其中 program-rust 目錄下是 Rust 開發(fā)的程序源代碼,client 目錄下是客戶端的源代碼。
example-helloworld | +-- src | | | +-- client | | | | | +-- hello_world.ts | | | | | +-- main.ts | | | | | +-- utils.ts | | | +-- program-rust | | | | | +-- src | | | | | | | +-- lib.rs | | | | | +-- tests | | | | | | | +-- lib.rs | | | | | +-- Cargo.toml | | | | | +-- Xargo.toml | +-- .gitignore | +-- package.json | +-- tsconfig.json
1. 鏈上程序源碼解讀
program-rust/src/lib.rs 是鏈上程序的核心代碼,如代碼清單 1-1 所示,實(shí)現(xiàn)了將程序被調(diào)用次數(shù)存儲(chǔ)在鏈上賬戶中。
第 1 行代碼將 borsh::BorshDeserialize 和 borsh::BorshSerialize 引入本地作用域,用于序列化和反序列化數(shù)據(jù)。第 2~9 行代碼將 Solana Rust SDK 的模塊引入本地作用域,使用 Rust 編寫程序都需要這個(gè) SDK。
第 13~16 行代碼定義了 GreetingAccount 結(jié)構(gòu)體作為存儲(chǔ)在賬戶中的狀態(tài)類型,里面有一個(gè) u32 類型的字段 counter,用于記錄程序被有效調(diào)用的次數(shù)。
第 19 行代碼 entrypoint 聲明了 process_instruction 函數(shù)是程序入口,每個(gè)程序都有一個(gè)唯一的入口。第 22~26 行代碼是 process_instruction 函數(shù)簽名,它要接收 3 個(gè)參數(shù):
- program_id:鏈上程序的部署地址,在這里也就是 helloworld 程序賬戶的公鑰。
- accounts:與程序交互的賬戶列表,當(dāng)前程序會(huì)使用賬戶列表中的賬戶來存儲(chǔ)狀態(tài)或修改賬戶中的數(shù)據(jù)。如果當(dāng)前程序不是某個(gè)賬戶的所有者,那就無法使用該賬戶存儲(chǔ)狀態(tài)或修改數(shù)據(jù),當(dāng)前交易會(huì)執(zhí)行失敗。
- instruction_data:指令數(shù)據(jù),比如要轉(zhuǎn)賬的代幣數(shù)量、轉(zhuǎn)賬地址等。
process_instruction 函數(shù)的返回值類型是 ProgramResult,ProgramResult 類型的定義如下所示。
pub type ProgramResult = Result<(), ProgramError>;
當(dāng)程序的邏輯執(zhí)行成功時(shí)返回 Ok(()),否則將 ProgramError 錯(cuò)誤返回。ProgramError 是自定義錯(cuò)誤的枚舉類型,其中包含程序可能失敗的各種原因。
第 27 行代碼使用 msg! 宏將字符串輸出到日志中,方便觀察業(yè)務(wù)的執(zhí)行邏輯和調(diào)試信息。第 30 行代碼通過 iter 方法將賬戶列表轉(zhuǎn)換為迭代器,以安全的方式獲取賬戶地址。第 33 行代碼使用了 ? 操作符,如果迭代器中有賬戶地址,會(huì)將賬戶地址與變量 account 綁定。如果迭代器中沒有賬戶地址,? 操作符會(huì)讓程序執(zhí)行失敗。
第 36~39 行代碼判斷存儲(chǔ)狀態(tài)的賬戶所有者是否是當(dāng)前程序。只有賬戶所有者才能修改數(shù)據(jù),否則輸出日志并返回。
第 42~44 行代碼先對(duì)賬戶中的數(shù)據(jù)進(jìn)行反序列化操作,再將 counter 加一,最后將其序列化后存儲(chǔ)到賬戶中。
代碼清單 1-1 helloworld 鏈上程序
use borsh::{BorshDeserialize, BorshSerialize}; use solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint, entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey, }; /// Define the type of state stored in accounts #[derive(BorshSerialize, BorshDeserialize, Debug)] pub struct GreetingAccount { /// number of greetings pub counter: u32, } // Declare and export the program's entrypoint entrypoint!(process_instruction); // Program entrypoint's implementation pub fn process_instruction( program_id: &Pubkey, // Public key of the account the hello world program was loaded into accounts: &[AccountInfo], // The account to say hello to _instruction_data: &[u8], // Ignored, all helloworld instructions are hellos ) -> ProgramResult { msg!("Hello World Rust program entrypoint"); // Iterating accounts is safer then indexing let accounts_iter = &mut accounts.iter(); // Get the account to say hello to let account = next_account_info(accounts_iter)?; // The account must be owned by the program in order to modify its data if account.owner != program_id { msg!("Greeted account does not have the correct program id"); return Err(ProgramError::IncorrectProgramId); } // Increment and store the number of times the account has been greeted let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?; greeting_account.counter += 1; greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?; msg!("Greeted {} time(s)!", greeting_account.counter); Ok(()) }
2. 客戶端程序源碼解讀
要想測(cè)試鏈上程序,我們必須通過 Solana JSON RPC API 去和鏈上程序進(jìn)行交互。example-helloworld 項(xiàng)目提供的客戶端用 Typescript 編寫,使用了 web3.js 庫這個(gè) Solana JavaScript SDK。
在 client 目錄下,客戶端執(zhí)行的入口是 main.ts 文件,它按特定的順序執(zhí)行任務(wù),每個(gè)任務(wù)的業(yè)務(wù)邏輯代碼在 hello_world.ts 文件。
首先,客戶端調(diào)用 establishConnection 函數(shù)與集群建立連接。
export async function establishConnection(): Promise<void> { const rpcUrl = await getRpcUrl(); connection = new Connection(rpcUrl, 'confirmed'); const version = await connection.getVersion(); console.log('Connection to cluster established:', rpcUrl, version); }
接著,客戶端調(diào)用 establishPayer 函數(shù)來確保有一個(gè)有支付能力的賬戶。
export async function establishPayer(): Promise<void> { let fees = 0; if (!payer) { const {feeCalculator} = await connection.getRecentBlockhash(); // Calculate the cost to fund the greeter account fees += await connection.getMinimumBalanceForRentExemption(GREETING_SIZE); // Calculate the cost of sending transactions fees += feeCalculator.lamportsPerSignature * 100; // wag try { // Get payer from cli config payer = await getPayer(); } catch (err) { // Fund a new payer via airdrop payer = await newAccountWithLamports(connection, fees); } } const lamports = await connection.getBalance(payer.publicKey); if (lamports < fees) { // This should only happen when using cli config keypair const sig = await connection.requestAirdrop( payer.publicKey, fees - lamports, ); await connection.confirmTransaction(sig); } console.log( 'Using account', payer.publicKey.toBase58(), 'containing', lamports / LAMPORTS_PER_SOL, 'SOL to pay for fees', ); }
然后,客戶端調(diào)用 checkProgram 函數(shù)從 src/program-rust/target/deploy/helloworld-keypair.json 中加載已部署程序的密鑰對(duì)(此操作前需先構(gòu)建鏈上程序,詳見 1.3.2 節(jié)),并使用密鑰對(duì)的公鑰來獲取程序賬戶。如果程序不存在,客戶端會(huì)報(bào)錯(cuò)并停止執(zhí)行。如果程序存在,將創(chuàng)建一個(gè)新賬戶來存儲(chǔ)狀態(tài),并以該程序作為新賬戶所有者。這里新賬戶存儲(chǔ)的狀態(tài),就是程序被調(diào)用的次數(shù)。
export async function checkProgram(): Promise<void> { // Read program id from keypair file try { const programKeypair = await createKeypairFromFile(PROGRAM_KEYPAIR_PATH); programId = programKeypair.publicKey; } catch (err) { const errMsg = (err as Error).message; throw new Error( `Failed to read program keypair at '${PROGRAM_KEYPAIR_PATH}' due to error: ${errMsg}.`, ); } // Check if the program has been deployed const programInfo = await connection.getAccountInfo(programId); if (programInfo === null) { if (fs.existsSync(PROGRAM_SO_PATH)) { throw new Error( 'Program needs to be deployed with `solana program deploy dist/program/helloworld.so`', ); } else { throw new Error('Program needs to be built and deployed'); } } else if (!programInfo.executable) { throw new Error(`Program is not executable`); } console.log(`Using program ${programId.toBase58()}`); // Derive the address (public key) of a greeting account from the program so that it's easy to find later. const GREETING_SEED = 'hello'; greetedPubkey = await PublicKey.createWithSeed( payer.publicKey, GREETING_SEED, programId, ); // Check if the greeting account has already been created const greetedAccount = await connection.getAccountInfo(greetedPubkey); if (greetedAccount === null) { console.log( 'Creating account', greetedPubkey.toBase58(), 'to say hello to', ); const lamports = await connection.getMinimumBalanceForRentExemption( GREETING_SIZE, ); const transaction = new Transaction().add( SystemProgram.createAccountWithSeed({ fromPubkey: payer.publicKey, basePubkey: payer.publicKey, seed: GREETING_SEED, newAccountPubkey: greetedPubkey, lamports, space: GREETING_SIZE, programId, }), ); await sendAndConfirmTransaction(connection, transaction, [payer]); } }
客戶端再調(diào)用 sayHello 函數(shù)向鏈上程序發(fā)送交易。一個(gè)交易可以包含一個(gè)或多個(gè)不同的指令,當(dāng)前該交易包含了一個(gè)指令,指令中帶有要調(diào)用鏈上程序的 Program Id 以及客戶端要交互的賬戶地址。需要注意的是,如果交易中包含多個(gè)不同的指令,其中有一個(gè)指令執(zhí)行失敗,那么所有指令所做的操作都會(huì)被還原。
export async function sayHello(): Promise<void> { console.log('Saying hello to', greetedPubkey.toBase58()); const instruction = new TransactionInstruction({ keys: [{pubkey: greetedPubkey, isSigner: false, isWritable: true}], programId, data: Buffer.alloc(0), // All instructions are hellos }); await sendAndConfirmTransaction( connection, new Transaction().add(instruction), [payer], ); }
最后,客戶端調(diào)用 reportGreetings 函數(shù)訪問賬戶數(shù)據(jù),查詢鏈上程序被有效調(diào)用的次數(shù)。
export async function reportGreetings(): Promise<void> { const accountInfo = await connection.getAccountInfo(greetedPubkey); if (accountInfo === null) { throw 'Error: cannot find the greeted account'; } const greeting = borsh.deserialize( GreetingSchema, GreetingAccount, accountInfo.data, ); console.log( greetedPubkey.toBase58(), 'has been greeted', greeting.counter, 'time(s)', ); }
1.3.2 Hello World 構(gòu)建與部署
1. 創(chuàng)建項(xiàng)目
使用 git clone 命令下載 example-helloworld 項(xiàng)目。
git clone https://github.com/solana-labs/example-helloworld.git cd example-helloworld
2. 構(gòu)建鏈上程序
運(yùn)行以下命令,在 program-rust 目錄下構(gòu)建鏈上程序。
cd src/program-rust/ cargo build-bpf
構(gòu)建完成后,src/program-rust/target/deploy 目錄下的 helloworld.so 就是可在 Solana 集群部署的鏈上程序的 BPF 字節(jié)碼文件。
3. 啟動(dòng)本地集群
當(dāng)前項(xiàng)目在本地集群部署運(yùn)行,因此首先選擇 localhost 集群,運(yùn)行以下命令。
solana config set --url localhost
本地集群設(shè)置成功,會(huì)出現(xiàn)以下內(nèi)容。
Config File: ~/.config/solana/cli/config.yml RPC URL: http://localhost:8899 WebSocket URL: ws://localhost:8900/ (computed) Keypair Path: ~/.config/solana/id.json Commitment: confirmed
再運(yùn)行以下命令,啟動(dòng) localhost 集群。
solana-test-validator
看到以下內(nèi)容,代表本地集群已成功啟動(dòng)。
Ledger location: test-ledger Log: test-ledger/validator.log Identity: A4HuRgmABNCe94epY2mU7q6WqEHCo2B9iBFE5Yphiw5u Genesis Hash: 96TF9n1uuyFv4rAKECffA61jLrgYjMjNRZ3hJpP6HSr7 Version: 1.7.18 Shred Version: 13390 Gossip Address: 127.0.0.1:1024 TPU Address: 127.0.0.1:1027 JSON RPC URL: http://127.0.0.1:8899 ? 00:00:42 | Processed Slot: 45430 | Confirmed Slot: 45430 | Finalized Slot: 45398 | Snapshot Slot: 45300 | Transactions: 45452 | ◎499.772930000
4. 部署鏈上程序
運(yùn)行以下命令,在 localhost 集群部署鏈上程序。
solana program deploy target/deploy/helloworld.so // Program Id: 6AArMEBpFhhtU2mBnEMEPeEH7xkhfUwPseUeG4fhLYto
鏈上程序部署成功會(huì)返回 Program Id,它類似于以太坊智能合約的地址。
5. 調(diào)用鏈上程序
helloworld 已成功部署,可以與它進(jìn)行交互了!example-helloworld 項(xiàng)目提供了一個(gè)簡(jiǎn)單的客戶端,在運(yùn)行客戶端之前先安裝依賴軟件包。
npm install
由于我們調(diào)整了鏈上程序的構(gòu)建方式,沒有使用該項(xiàng)目默認(rèn)的 npm run build:program-rust 命令,因此需要修改 client 目錄下的 hello_world.ts 文件,將第 48 行代碼定義的變量 PROGRAM_PATH 的路徑由“../../dist/program”改為“../program-rust/target/deploy”。 再運(yùn)行以下命令,啟動(dòng)客戶端去調(diào)用鏈上程序。
npm run start
客戶端成功調(diào)用鏈上程序,輸出內(nèi)容如下所示。如果再次運(yùn)行客戶端,第 10 行所顯示的次數(shù)會(huì)加一。至此,我們已經(jīng)成功在 Solana 集群部署鏈上程序并與之交互了。
> helloworld@0.0.1 start > ts-node src/client/main.ts Let's say hello to a Solana account... Connection to cluster established: http://localhost:8899 { 'feature-set': 3179062686, 'solana-core': '1.6.23' } Using account 4xRm2FYmRB8WdxJk6nXicVMgsPnsxChEnpQwFDGwdcSS containing 499999999.93435186 SOL to pay for fees Using program 6AArMEBpFhhtU2mBnEMEPeEH7xkhfUwPseUeG4fhLYto Creating account Eq7bcsg5p6AaYiPnfiia99ESsuq4B4jYpVbWZhQ94Zvy to say hello to Saying hello to Eq7bcsg5p6AaYiPnfiia99ESsuq4B4jYpVbWZhQ94Zvy Eq7bcsg5p6AaYiPnfiia99ESsuq4B4jYpVbWZhQ94Zvy has been greeted 1 time(s) Success
如果沒有輸出期望值,請(qǐng)首先確認(rèn)是否已正確啟動(dòng)了本地集群,構(gòu)建并部署好了鏈上程序。此外,可以運(yùn)行以下命令查看程序日志,日志包括程序日志消息以及程序失敗信息。
solana logs
包含程序失敗信息的日志如下所示,檢查日志找出程序失敗的原因。
<img src="https://learnblockchain.cn/css/default/copy.svg" /><code>Transaction executed in slot 5621: Signature: 4pya5iyvNfAZj9sVWHzByrxdKB84uA5sCxLceBwr9UyuETX2QwnKg56MgBKWSM4breVRzHmpb1EZQXFPPmJnEtsJ Status: Error processing Instruction 0: Program failed to complete Log Messages: Program G5bbS1ipWzqQhekkiCLn6u7Y1jJdnGK85ceSYLx2kKbA invoke [1] Program log: Hello World Rust program entrypoint Program G5bbS1ipWzqQhekkiCLn6u7Y1jJdnGK85ceSYLx2kKbA consumed 200000 of 200000 compute units Program failed to complete: exceeded maximum number of instructions allowed (200000) at instruction #334 Program G5bbS1ipWzqQhekkiCLn6u7Y1jJdnGK85ceSYLx2kKbA failed: Program failed to complete
1.4 本章小節(jié)
本章對(duì) Solana 區(qū)塊鏈的基本概念進(jìn)行了簡(jiǎn)要介紹,Solana 的智能合約叫做鏈上程序。在開始 Solana 鏈上程序開發(fā)之前,需要先安裝和配置相關(guān)的編程環(huán)境,我們著重介紹了 Solana CLI 的安裝和配置。
Hello World 是一個(gè)官方演示項(xiàng)目,通過對(duì)這個(gè)項(xiàng)目源碼的解讀,我們了解了如何使用 Rust 開發(fā)鏈上程序,并使用 Solana CLI 來構(gòu)建與部署,以及使用 Solana JavaScript SDK 與鏈上程序進(jìn)行交互。
你可能感興趣的文章
-
SOL價(jià)格上漲和網(wǎng)絡(luò)擁堵 Solana需要Layer2和Rollup么?
DRiP的創(chuàng)始人Vibhu在一則聲明中引發(fā)了一場(chǎng)迫切需要的辯論:Solana需要有L2和Rollup,本文將從Solana和擁堵、使Solana模塊化、Solana 應(yīng)用鏈、Solana 第二層、推動(dòng)Rollup和Ap…
2024-04-28 -
SOL是公鏈嗎?Solana(SOL)公鏈全面介紹
Solana是一個(gè)于2017年成立的可程式設(shè)計(jì)的區(qū)塊鏈,Solana 簡(jiǎn)稱SOL鏈,而SOL幣則是該公鏈的原生加密貨幣,在SOL鏈上的一切交互都需要使用SOL幣支付手續(xù)費(fèi),那么,SOL是公鏈嗎?…
2024-04-17 -
Jupiter是投資Solana的放大器嗎?
Jupiter 是 Solana 生態(tài)核心流動(dòng)性聚合器,提供最廣泛的代幣索引,以及任意交易對(duì)的最優(yōu)路徑,那么,Jupiter是投資Solana的放大器嗎?下文將為大家詳細(xì)分析…
2024-04-16 -
2024年Solana生態(tài)值得關(guān)注的10大DeFi主題
Solana 擁有最強(qiáng)大的聚合器之一,其中 Jupiter 處于領(lǐng)先地位,本文將為大家詳細(xì)介紹2024年Solana生態(tài)值得關(guān)注的10大DeFi主題…
2024-03-28 -
以太坊殺手Solana為什么會(huì)暴漲24倍?以太坊殺手幣行情解析
以太坊和覬覦它的「殺手」們都怎么樣了?以太坊殺手Solana近期漲勢(shì)喜人,一年多時(shí)間直接從9美元漲到208刀,是什么原因支撐以太坊的漲勢(shì)的?和以太坊殺手Solana們都有這樣的…
2024-03-27 -
Ton會(huì)不會(huì)成為Solana挑戰(zhàn)者?TON幣未來行情分析
Ton還有投資價(jià)值嗎?未來的Ton幣怎么樣?最近,TON 公鏈在經(jīng)歷長(zhǎng)時(shí)間低迷后終于開始起步,總鎖倉量突破 7800 萬,創(chuàng)歷史新高,Ton 能否復(fù)刻 Solana Meme 狂潮?大家一起看看…
2024-03-26 -
2024年最受歡迎的區(qū)塊鏈為什么是Solana(SOL幣)?詳細(xì)解讀Solana(SOL幣)
Solana(SOL幣)為什么是2024年最受歡迎的區(qū)塊鏈?Solana生態(tài)系統(tǒng)已成為今年迄今為止最受歡迎的區(qū)塊鏈生態(tài)系統(tǒng),占全球加密貨幣投資者對(duì)特定鏈敘事興趣的49.3%,從1月1日101.4…
2024-03-25 -
一文讀懂什么是Solscan以及如何使用?
Solscan是一款Solana區(qū)塊鏈探索器,它為用戶提供了一個(gè)基于網(wǎng)絡(luò)的平臺(tái),可用于探索和分析Solana區(qū)塊鏈上的交易、錢包地址、合約、NFT 和 DeFi 項(xiàng)目,本文將為大家詳細(xì)介紹什…
2024-03-25 -
Solana的黃金時(shí)代:新故事,新起點(diǎn)
SOL再破200美元大關(guān),較歷史高點(diǎn)僅剩30%的漲幅,本文將為大家深度解讀Solana的黃金時(shí)代:新故事,新起點(diǎn)…
2024-03-20 -
Bitget研究院:以太坊完成坎昆升級(jí)帶動(dòng)Solana生態(tài)普漲
過去24小時(shí),市場(chǎng)出現(xiàn)了不少新的熱門幣種和話題,或許它們就是下一個(gè)造富機(jī)會(huì),本文將根據(jù)現(xiàn)在的市場(chǎng)環(huán)境,分析下面要異動(dòng)的板塊代幣及用戶熱搜代幣介紹…
2024-03-15