关于区块链共识机制
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:实时,无许可,产生真实的货币交易的场所。
根据功能可以区分为:
每个不可执行账户还有不同类型:
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 字段包含了此账户的所有者,由程序账户的地址表示。关于账户所有者有一些规则:
rent_epoch 字段指示此账户将在下一个epoch 时期欠租金。一个epoch是 leader 调度的插槽数。与操作系统中的传统文件不同,Solana上的账户具有以lamports表示的寿命。账户的持续存在取决于其lamport余额,这让我们引入了租金的概念。
is_signer 字段是一个布尔值,指示交易是否已由涉及账户的所有者签名。换句话说,它告诉交易中涉及的程序账户,账户是否是签名者。作为签名者意味着账户持有公钥对应的私钥,并有权批准提议的交易。
is_writable 字段是一个布尔值,指示账户的数据是否可以修改。Solana允许交易将账户指定为只读,以促进并行处理。虽然运行时允许不同程序同时访问只读账户,但它使用交易处理顺序处理潜在的可写账户写入冲突。这确保只有非冲突的交易可以并行处理。
executable 字段是一个布尔值,指示账户是否可以处理指令。是的,这意味着程序存储在账户中,我们将在下一节中深入探讨这一点。首先,我们需要介绍租金的概念。
保持账户活跃并确保账户保存在验证者内存中而产生的存储成本。租金的收取取决于epoch评估,他是由时间段定义的时间单位。
可以使用getMinimumBalanceForRentExemption RPC端点来估算特定账户大小的租金。Test Drive通过接受usize中的账户数据长度来简化此过程。Solana rent CLI子命令也可以用于估算账户成为租金豁免所需的最低SOL金额。例如,在撰写本文时,运行命令solana rent 20000将返回租金豁免最低值:0.14009088 SOL。
Solana上有两种“类型”地址。
Solana使用ed25519,一种使用SHA-512(SHA-2)和Curve22519椭圆曲线的EdDSA签名方案来创建地址。 生成 32 字节的公钥,它们作为主要地址格式可以直接使用,因为它们没有被哈希。
为了使地址有效,它必须是ed25519曲线上的一个点。然而,并非所有地址都需要从此曲线派生。程序派生地址(PDA)是在曲线之外生成的,这意味着它们没有对应的私钥,也不能用于签名。PDAs是通过系统程序创建的,当程序需要管理账户时使用。
以太坊包含两种账户(EOA、合约账户),合约账户有合约代码管理,不能发起交易。
Solana任何账户都可能成为程序。代码与数据分离。
solana没有状态,与各种数据账户交互,无需冗余部署。在不同程序之间交互无需转移资产。
Solana需要支付租金,要求有个最低余额,以保持活动状态。不使用或资金不足会被回收。
程序是由BPF Loader拥有的可执行账户。它们由Solana Runtime执行,该运行时旨在处理交易和程序逻辑。
Solana编程模型特点:代码与数据分离。程序没有状态,不存储状态。所有数据存在账户中,通过交易以引用的方式传给程序。
Solana程序能力:
程序的两种类型
通常使用rsut语言进行程序开发,借助开发框架: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运行时将删除该账户。这个假设在交易之间是有效的,但在指令之间或跨程序调用(我们将在以后的文章中介绍跨程序调用)是无效的。程序应明确将账户的数据清零,以加固去初始化过程中的潜在缺陷。否则,攻击者可以发出后续指令来利用假定的删除,例如在交易完成之前重新使用该账户。