關於區塊鏈共識機制
pow、pos、poa、poh
Solana 全方位介紹 —— 共識、錢包、生態、合約 | 登鏈社區 | 區塊鏈技術社區
指南:用 Anchor 構建 Solana 程式 | 登鏈社區 | 區塊鏈技術社區
3 分鐘在 Solana 鏈創建代幣教程:無代碼發幣平台 PandaTool 支持 | 登鏈社區 | 區塊鏈技術社區
新的共識機制 —— 歷史證明機制(PoH)
世界上最快的 “高速公鏈”——Solana
Solana 的基礎共識為 PoS(即常見的權益證明機制),簡單來說,就是根據用戶持有貨幣的多少和時間(幣齡)來決定發放利息額度的制度。
歷史證明機制(PoH)是 Solana 一個巧妙又實用性極高的創新。傳統的區塊鏈,比如比特幣和以太坊,將時間和狀態耦合在一起,只有新區塊誕生才能產生全局一致的狀態。Solana 則巧妙得將基於哈希的時間鏈和狀態分離,不是將每個區塊的哈希鏈接在一起,而是網絡中的驗證者對區塊內的哈希本身進行哈希,這種機制就是 PoH(Proof of history)。
Solana 開發學習筆記 (一)—— 從 Hello World 出發 | 登鏈社區 | 區塊鏈技術社區
Solana 編程模型:Solana 開發入門 | 登鏈社區 | 區塊鏈技術社區
集群#
是 Solana 架構的核心,一組驗證者共同處理交易並維護單個分類帳(ledger)。Solana 有幾個不同的集群,每個集群都有特定的用途:
本地主機:默認端口 8899 的本地開發集群。Solana 命令行界面 (CLI) 有一個內置的測試驗證者,根據開發者個人需求可定制,無需空投沒有速率限制。
開發網絡 Devent:進行測試和實驗的無價值環境
測試網絡 Testnet:核心人員實驗新更新和功能的場所,也可進行性能測試。
主網測試版 Mainnet Beta:實時,無許可,產生真實的貨幣交易的場所。
Solana 賬戶#
- 存儲數據的賬戶
- 存儲可執行程序的賬戶
- 存儲原生程序的賬戶
根據功能可以區分為:
- 可執行賬戶
- 不可執行賬戶 (不包含代碼)
每個不可執行賬戶還有不同類型:
- 關聯 Token 賬戶 - 一個包含特定代幣信息、其餘額和所有者信息的賬戶(例如,Alice 擁有 10 個 USDC)
- 系統賬戶 - 由系統程序創建和擁有的賬戶
- 質押賬戶 - 用於將代幣委託給驗證者以潛在獲得獎勵的賬戶
賬戶結構#
pub struct AccountInfo<'a> {
pub key: &'a Pubkey,
pub lamports: Rc>,
pub data: Rc>,
pub owner: &'a Pubkey,
pub rent_epoch: Epoch,
pub is_signer: bool,
pub is_writable: bool,
pub executable: bool,
}
賬戶通過其地址(key)進行標識,這是一個唯一的 32 字節公鑰。
lamports 字段保存著該賬戶擁有的lamports數量。一個 lamport 等於 Solana 的原生代幣 SOL 的十億分之一。
data 指的是由該賬戶存儲的原始數據字節數組。它可以存儲從數字資產的元數據到代幣餘額等任何內容,並可由程序進行修改。
owner 字段包含了此賬戶的所有者,由程序賬戶的地址表示。關於賬戶所有者有一些規則:
- 只有賬戶的所有者才能更改其數據並提取 lamports
- 任何人都可以向賬戶存入 lamports
- 賬戶的所有者可以將所有權轉移給新所有者,前提是賬戶的數據被重置為零
rent_epoch 字段指示此賬戶將在下個 epoch 時期欠租金。一個epoch是 leader 調度的插槽數。與操作系統中的傳統文件不同,Solana 上的賬戶具有以 lamports 表示的壽命。賬戶的持續存在取決於其 lamport 餘額,這讓我們引入了租金的概念。
is_signer 字段是一個布爾值,指示交易是否已由涉及賬戶的所有者簽名。換句話說,它告訴交易中涉及的程序賬戶,賬戶是否是簽名者。作為簽名者意味著賬戶持有公鑰對應的私鑰,並有權批准提議的交易。
is_writable 字段是一個布爾值,指示賬戶的數據是否可以修改。Solana 允許交易將賬戶指定為只讀,以促進並行處理。雖然運行時允許不同程序同時訪問只讀賬戶,但它使用交易處理順序處理潛在的可寫賬戶寫入衝突。這確保只有非衝突的交易可以並行處理。
executable 字段是一個布爾值,指示賬戶是否可以處理指令。是的,這意味著程序存儲在賬戶中,我們將在下一節中深入探討這一點。首先,我們需要介紹租金的概念。
租金 (Rent)#
保持賬戶活躍並確保賬戶保存在驗證者內存中而產生的存儲成本。租金的收取取決於 epoch 評估,他是由時間段定義的時間單位。
- 租金收取 - 租金每個 epoch 收取一次。當賬戶被交易引用時,也可以收取租金
- 租金分配 - 收取的一部分租金被銷毀,意味著它被永久性地從流通中移除。其餘部分在每個插槽後分配給投票賬戶
- 租金支付 - 如果一個賬戶沒有足夠的 lamports 來支付租金,那麼它的數據將被移除,並且賬戶將在一個被稱為垃圾回收的過程中被取消分配
- 租金豁免 - 如果賬戶保持等於兩年租金支付的最低餘額,則賬戶可以成為租金豁免。所有新賬戶必須滿足此租金豁免門檻,這取決於賬戶的大小
- 租金檢索 - 用戶可以關閉一個賬戶以取回其剩餘的 lamports。這允許用戶檢索存儲在賬戶中的租金
可以使用 getMinimumBalanceForRentExemption RPC 端點來估算特定賬戶大小的租金。Test Drive通過接受 usize 中的賬戶數據長度來簡化此過程。Solana rent CLI 子命令也可以用於估算賬戶成為租金豁免所需的最低 SOL 金額。例如,在撰寫本文時,運行命令 solana rent 20000 將返回租金豁免最低值:0.14009088 SOL。
Solana 地址#
Solana 上有兩種 “類型” 地址。
Solana 使用ed25519,一種使用SHA-512(SHA-2)和Curve22519橢圓曲線的 EdDSA 簽名方案來創建地址。 生成 32 字節的公鑰,它們作為主要地址格式可以直接使用,因為它們沒有被哈希。
為了使地址有效,它必須是ed25519曲線上的一個點。然而,並非所有地址都需要從此曲線派生。程序派生地址(PDA)是在曲線之外生成的,這意味著它們沒有對應的私鑰,也不能用於簽名。PDAs 是通過系統程序創建的,當程序需要管理賬戶時使用。
Solana 賬戶與以太坊賬戶的不同#
以太坊包含兩種賬戶 (EOA、合約賬戶),合約賬戶有合約代碼管理,不能發起交易。
Solana 任何賬戶都可能成為程序。代碼與數據分離。
solana 沒有狀態,與各種數據賬戶交互,無需冗餘部署。在不同程序之間交互無需轉移資產。
Solana 需要支付租金,要求有個最低餘額,以保持活動狀態。不使用或資金不足會被回收。
什麼是程序#
程序是由BPF Loader擁有的可執行賬戶。它們由Solana Runtime執行,該運行時旨在處理交易和程序邏輯。
Solana 編程模型特點:代碼與數據分離。程序沒有狀態,不存儲狀態。所有數據存在賬戶中,通過交易以引用的方式傳給程序。
Solana 程序能力:
- 擁有額外賬戶
- 從其他賬戶存取 / 讀取資金
- 修改數據 / 扣除擁有的賬戶
程序的兩種類型
- 鏈上程序 - 部署在 Solana 上的用戶編寫的程序。它們可以由其升級權限進行升級,升級權限通常是部署程序的賬戶
- 原生程序 - 這些程序集成到 Solana 核心(Core)中。它們提供驗證者運行所需的基本功能。原生程序只能通過網絡範圍的軟件更新進行升級。常見的示例包括系統程序、BPF Loader 程序和投票程序。
通常使用 rust 語言進行程序開發,借助開發框架:Anchor,來簡化程序創建
什麼是交易#
是鏈上活動的支柱。是調用程序和實施狀態更改的機制。solana 上的交易是一系列指令的捆綁。
交易的組成:
- 一個要讀取或寫入的賬戶數組
- 一個或多個指令
- 一個或多個簽名
Solana 交易結構,提供了網絡處理和驗證操作所需的信息
pub struct Transaction {
pub signatures: Vec,
pub message: Message,
}
signatures 字段包含與序列化 Message 對應的一組簽名。每個簽名與 Message 的 account_keys 列表中的一個賬戶密鑰相關聯,從 fee payer 開始。 fee payer 是在處理交易時負責支付交易費用的賬戶。這通常是發起交易的賬戶。所需簽名的數量等於消息的 MessageHeader 中定義的 num_required_signatures。
message 本身是類型為 Message 的結構。它定義如下:
pub struct Message {
pub header: MessageHeader,
pub account_keys: Vec,
pub recent_blockhash: Hash,
pub instructions: Vec,
}
header 包含三個無符號 8 位整數:所需簽名的數量(即 num_required_signatures)、只讀簽名者的數量和只讀非簽名者的數量。
account_keys 字段列出了交易中涉及的所有賬戶地址。請求讀寫訪問權限的賬戶首先出現,然後是只讀賬戶。
recent_blockhash 是一個最近的區塊哈希,包含一個 32 字節的 SHA-256 哈希。這是為了指示客戶端上次觀察到賬本的時間,並作為最近交易的生命周期。驗證者將拒絕具有舊區塊哈希的交易。此外,最近區塊哈希的包含有助於防止重複交易,因為任何與先前完全相同的交易都將被拒絕。如果出於任何原因,交易需要在提交到網絡之前很長時間簽名,可以使用持久交易 nonce來代替最近的區塊哈希,以確保它是唯一的交易。
instructions 字段包含一個或多個 CompiledInstruction 結構,每個結構都指示網絡驗證者執行特定操作。
指令#
指令是對單個 Solana 程序調用的指令。它是程序中執行邏輯的最小單位,也是 Solana 上最基本的操作單元。程序解釋從指令傳遞的數據,並對指定的賬戶進行操作。Instruction 結構定義如下:
pub struct Instruction {
pub program_id: Pubkey,
pub accounts: Vec,
pub data: Vec,
}
program_id 字段指定要執行的程序的公鑰。這是將處理指令的程序的地址。由該公鑰指示的程序賬戶的所有者指定了負責初始化和執行程序的加載器。加載器一旦部署,就會將鏈上 Solana 字節碼格式(SBF)程序標記為可執行。Solana 的運行時將拒絕任何試圖調用未標記為可執行的賬戶的交易。
accounts 字段列出了指令可能從中讀取或寫入的賬戶。這些賬戶必須作為 AccountMeta 值提供。任何可能被指令改變數據的賬戶必須被指定為可寫,否則交易將失敗。這是因為程序不能向它們不擁有或沒有必要權限的賬戶寫入。這也適用於改變賬戶的 lamports:從程序不擁有的賬戶中減去 lamports 將導致交易失敗,而向任何賬戶添加 lamports 是允許的。accounts 字段還可以指定程序不會讀取或寫入的賬戶。這是為了通過運行時影響程序執行的調度,但是這些賬戶將被忽略。
data 是一個包含 8 位無符號整數的通用向量,用作傳遞給程序的輸入。該字段至關重要,因為它包含程序將執行的編碼指令。
Solana 對指令數據的格式是不可知的。但是,它內置了對 bincode 和 borsh(用於哈希的二進制對象表示序列化器)的支持。序列化是將複雜數據結構轉換為一系列可以傳輸或存儲的字節的過程。數據的編碼方式選擇應考慮解碼的開銷,因為所有這些都發生在鏈上。通常更傾向於使用Borsh序列化,而不是bincode,因為它具有穩定的規範,JavaScript 實現,並且通常更有效。
程序使用輔助函數來簡化支持指令的構建。例如,系統程序提供了一個輔助函數來構建 SystemInstruction::Assign 指令:
pub fn assign(pubkey: &Pubkey, owner: &Pubkey) -> Instruction {
let account_metas = vec![AccountMeta::new(*pubkey, true)];
Instruction::new(
system_program::id(),
&SystemInstruction::Assign { owner: *owner },
account_metas,
)
}
該函數構造一個指令,當處理時,將把指定賬戶的所有者更改為提供的新所有者。
單個交易可以包含多個指令,這些指令按順序依次執行並具有原子性。這意味著要麼所有指令成功,要麼都不成功。這也意味著指令的順序可能至關重要。程序必須經過加固,以安全地處理任何可能的指令序列,以防止任何潛在的利用。
例如,在去初始化期間,程序可能會嘗試通過將其 lamport 餘額設置為零來去初始化一個賬戶。這假設 Solana 運行時將刪除該賬戶。這個假設在交易之間是有效的,但在指令之間或跨程序調用(我們將在以後的文章中介紹跨程序調用)是無效的。程序應明確將賬戶的數據清零,以加固去初始化過程中的潛在缺陷。否則,攻擊者可以發出後續指令來利用假定的刪除,例如在交易完成之前重新使用該賬戶。