General
區塊鏈與Solana入門

區塊鏈與Solana入門

Solana 簡介

在開始於 Solana 上進行開發之前,您需要了解一些使 Solana 獨特的基本概念。本指南涵蓋了帳戶、交易、程式及其互動方式。

Solana 上的帳戶

Solana 的架構以帳戶為核心:這些帳戶是儲存區塊鏈上資訊的數據容器。可以將帳戶視為檔案系統中的個別檔案,每個檔案都有特定的屬性以及一個控制它的擁有者。

每個 Solana 帳戶都有相同的基本結構:

rust
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,
}

每個帳戶都有一個唯一的 32 字節地址,顯示為 base58 編碼的字串(例如,14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5)。此地址作為帳戶在區塊鏈上的標識符,用於定位特定數據。

帳戶最多可以儲存 10 MiB 的數據,這些數據可以是可執行的程式碼或特定程式的數據。

所有帳戶都需要根據其數據大小存入相應的 lamport 押金以成為「免租金」。"租金" 這個術語是歷史遺留的,因為 lamport 最初會在每個 epoch 從帳戶中扣除,但這一功能現在已被禁用。如今,押金更像是可退還的押金。只要您的帳戶保持其數據大小所需的最低餘額,它就會保持免租金並永久存在。如果您不再需要某個帳戶,可以關閉它並全額取回押金。

每個帳戶都由一個程式擁有,只有該擁有程式可以修改帳戶的數據或提取其 lamport。然而,任何人都可以增加帳戶的 lamport 餘額,這對於資助操作或支付租金而無需依賴直接調用程式本身非常有用。

簽署權限的運作方式會因擁有權而有所不同。由系統程式擁有的帳戶可以簽署交易以修改其自身數據、轉移擁有權或取回儲存的 lamports。一旦擁有權轉移到另一個程式,該程式將完全控制該帳戶,無論您是否仍然擁有私鑰。這種控制權的轉移是永久且不可逆的。

帳戶類型

最常見的帳戶類型是系統帳戶(System Account),它儲存 lamports(SOL 的最小單位),並由系統程式(System Program)擁有。這些帳戶作為基本錢包帳戶,供用戶直接用於發送和接收 SOL。

代幣帳戶(Token Accounts)具有專門用途,儲存 SPL 代幣資訊,包括擁有權和代幣元數據。這些帳戶由代幣程式(Token Program)擁有,並管理 Solana 生態系統中的所有代幣相關操作。代幣帳戶屬於 數據帳戶(Data Accounts)。

數據帳戶(Data Accounts)儲存應用程式特定的資訊,並由自定義程式擁有。這些帳戶保存您的應用程式狀態,其結構可以根據您的程式需求進行設計,從簡單的用戶檔案到複雜的財務數據。

最後,程式帳戶(Program Accounts)包含在 Solana 上運行的可執行代碼,這是智能合約所在的位置。這些帳戶被標記為 executable: true,並儲存處理指令和管理狀態的程式邏輯。

處理帳戶數據

以下是程式如何與帳戶數據互動的方式:

rust
#[derive(BorshSerialize, BorshDeserialize)]
pub struct UserAccount {
    pub name: String,
    pub balance: u64,
    pub posts: Vec<u32>,
}

pub fn update_user_data(accounts: &[AccountInfo], new_name: String) -> ProgramResult {
    let user_account = &accounts[0];
    
    // Deserialize existing data
    let mut user_data = UserAccount::try_from_slice(&user_account.data.borrow())?;
    
    // Modify the data
    user_data.name = new_name;
    
    // Serialize back to account
    user_data.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
    
    Ok(())
}

與僅需插入記錄的資料庫不同,Solana 帳戶必須在使用前明確創建並提供資金。

Solana 上的交易

Solana 交易是包含多個指令的原子操作。交易中的所有指令要麼一起成功,要麼一起失敗:不會有部分執行的情況。

一個交易包括:

  • 指令:需要執行的個別操作

  • 帳戶:每個指令需要讀取或寫入的特定帳戶

  • 簽署者:授權交易的帳戶

rust
Transaction {
    instructions: [
        // Instruction 1: Transfer SOL
        system_program::transfer(from_wallet, to_wallet, amount),
        
        // Instruction 2: Update user profile  
        my_program::update_profile(user_account, new_name),
        
        // Instruction 3: Log activity
        my_program::log_activity(activity_account, "transfer", amount),
    ],
    accounts: [from_wallet, to_wallet, user_account, activity_account]
    signers: [user_keypair],
}

交易要求及費用

交易的總大小限制為 1,232 字節,這限制了您可以包含的指令和帳戶數量。

交易中的每個指令需要三個基本組成部分:要調用的程式地址、指令將讀取或寫入的所有帳戶,以及任何額外的數據,例如函數參數。

指令會按照您在交易中指定的順序依次執行。

每筆交易會產生每個簽名 5,000 lamports 的基本費用,以補償驗證者處理您的交易。

您還可以支付可選的優先費用,以提高當前領導者快速處理您交易的可能性。此優先費用的計算方式為您的計算單位限制乘以您的計算單位價格(以微 lamports 為單位)。

text
prioritization_fee = compute_unit_limit × compute_unit_price

支付交易費用的帳戶必須由系統程式擁有,以確保其能正確授權付款。

Programs on Solana

Solana 上的程式本質上是無狀態的,這意味著它們在函數調用之間不會維持任何內部狀態。相反,它們接收帳戶作為輸入,處理這些帳戶中的數據,並返回修改後的結果。

這種無狀態設計確保了行為的可預測性,並啟用了強大的組合模式。

程式本身存儲在標記為 executable: true 的特殊帳戶中,這些帳戶包含在調用時執行的已編譯二進制代碼。

用戶通過發送包含特定指令的交易與這些程式交互,每個指令針對特定的程式功能,並附帶必要的帳戶數據和參數。

rust
use solana_program::prelude::*;

#[derive(BorshSerialize, BorshDeserialize)]
pub struct User {
    pub name: String,
    pub created_at: i64,
}

pub fn create_user(
    accounts: &[AccountInfo],
    name: String,
) -> ProgramResult {
    let user_account = &accounts[0];
    
    let user = User {
        name,
        created_at: Clock::get()?.unix_timestamp,
    };
    
    user.serialize(&mut &mut user_account.data.borrow_mut()[..])?;
    Ok(())
}

程式可以由指定的升級權限進行更新,讓開發者在部署後修復漏洞及新增功能。然而,移除此升級權限會使程式永久不可更改,為用戶提供程式碼永不更改的保證。

為了透明度及安全性,用戶可以通過可驗證的構建來確認鏈上程式與其公開的源代碼相符,確保部署的字節碼與發布的源代碼完全一致。

Program Derived Addresses (PDAs)

PDA 是通過確定性生成的地址,能夠實現強大的可編程模式。它們是使用種子和程式 ID 創建的,生成的地址不會有對應的私鑰。

PDA 使用 SHA-256 雜湊算法,並結合特定的輸入,包括自定義種子、一個用於確保結果不在曲線上的 bump 值、將擁有 PDA 的程式 ID,以及一個常量標記。

當雜湊生成一個在曲線上的地址(大約 50% 的情況下會發生)時,系統會從 bump 255 開始,依次遞減到 254、253 等,直到找到一個不在曲線上的結果。

rust
use solana_nostd_sha256::hashv;

const PDA_MARKER: &[u8; 21] = b"ProgramDerivedAddress";

let pda = hashv(&[
    seed_data.as_ref(),    // Your custom seeds
    &[bump],               // Bump to ensure off-curve
    program_id.as_ref(),   // Program that owns this PDA
    PDA_MARKER,
]);

優點

PDA 的確定性特性消除了存儲地址的需求:您可以隨時從相同的種子重新生成它們。

這創造了可預測的地址方案,類似於鏈上的哈希映射結構。更重要的是,程式可以為其自己的 PDA 簽名,實現自主資產管理而無需暴露私鑰:

rust
let seeds = &[b"vault", user.as_ref(), &[bump]];

invoke_signed(
    &transfer_instruction,
    &[from_pda, to_account, token_program],
    &[&seeds[..]], // Program proves PDA control
)?;

Cross Program Invocation (CPI)

CPI 允許程式在同一筆交易中調用其他程式,實現真正的可組合性,讓多個程式可以在無需外部協調的情況下原子性地互動。

這使開發者能夠通過組合現有程式來構建複雜的應用程式,而不是從頭開始重建功能。

CPI(跨程序調用)遵循與常規指令相同的模式,要求您指定目標程序、所需的帳戶以及指令數據,主要的區別在於它們可以在其他程序內執行。

調用程序在委派特定操作給專門程序的同時,仍然保持對流程的控制:

rust
let cpi_accounts = TransferAccounts {
    from: source_account.clone(),
    to: destination_account.clone(), 
    authority: authority_account.clone(),
};

let cpi_ctx = CpiContext::new(token_program.clone(), cpi_accounts);
token_program::cpi::transfer(cpi_ctx, amount)?;

限制與能力

原始交易簽署者在整個CPI鏈中保持其權限,允許程序無縫地代表用戶執行操作。

然而,程序最多只能進行4層深的CPI(A → B → C → D),以防止無限遞歸。在CPI中,程序還可以使用CpiContext::new_with_signer為其PDA(程序衍生地址)簽名,從而實現複雜的自動化操作。

這種可組合性使得在單個原子交易中跨多個程序進行複雜操作成為可能,從而使Solana應用程序高度模塊化和互操作性強。

Blueshift © 2025Commit: e573eab