Featured image of post 区块链共识简介(BTC、ETH、Solana)

区块链共识简介(BTC、ETH、Solana)

区块链一直是21世纪较为流行的技术,但其本质就是在分布式场景中如何达成共识。这与分布式事务,分布式系统设计异曲同工。

区块链

拜占庭将军问题

世界上只有一种共识协议,就是Paxos,其他的所有共识算法都是Paxos的退化版本。

虽然Paxos长久以来一直无敌于分布式共识,但paxos在提出时并不能解决拜占庭将军问题。今天就从区块链的角度了解区块链是如何看待分布式共识,并解决拜占庭问题。

**没有拜占庭的世界:**信息可能丢失也可能延迟,但不会被错误传递。

拜占庭将军问题:信息可能丢失也可能延迟,同时有人会故意破坏系统,故意传递错误的信息。

  1. 即使有恶意参与者也能正常工作
  2. 任何人都可以无需批准的加入
  3. 去中心化:完全分布式,无中心网络

比特币提出的方案:不是试图确定谁值得信任,而是让撒谎在经济上比说真话更昂贵。工作量证明通过要求参与者消耗真实的计算能量来提出更改。攻击者需要在电力上话费的成本远超过他们通过攻击所能获得的收益。

区块链基础

对于拜占庭问题,如果有f个叛徒,就至少需要有3f+1个参与者才能保证结果真实性。

共识机制

传统网络金融的解决方案

4个将军中有一个叛徒。叛徒告诉两个好人进攻,告诉另一个好人撤退。如果只有两个好人进攻。

此时:

好人1好人2好人3结果
提出:进攻
收到:3进攻
收到好人1进攻,坏人撤退收到好人1进攻,坏人撤退好人1误以为全部进攻,但2和3犹豫不觉重新投票
确认阶段:好人1 又收到了其他两个好人的犹豫答复无限重试

但paxos会导致决定无法确定(有意的创造了活锁),从而无法达成共识。

传统网络如何解决?

必须事前知道所有的参与者是谁,且不能丢失。

每个参与者给其他所有参与者发送相同的消息,此时在根据得到消息的结果来达成共识。

4个系统就需要,4*3=12次的消息传递,随着系统的增多和意见的不统一,则会更加可能出现问题。

网络通讯次数多,随着参与者的增多,达成共识的可能变成几乎不可能。

工作量证明(POW)

每个人想要得到自己的结论,必须进行一个昂贵的计算工作

  1. 矿工将待记账(确认收货)的交易收集到一个区块中
  2. 旷工必须找到一个随机数,和当前区块数据结合并进行hash运算,产生一个特殊(多个零开头)的结果
  3. 第一个找到答案的旷工将其方案广播到网络
  4. 其他参与者立刻验证解决方案的正确性并开始接收下一个新的区块(上一个区块确认收货,记账成功)下一个新区块的头包含上一次的hash值
  • 尝试用pow模拟拜占庭问题

先看上面的场景,假设4个将军,1个叛徒

由于工作量是概率问题,4个将军每个人记账成功的概率为25%,则目前好人算力为3,坏人算力为1

pow默认大家都是无限工作的,链是无限延伸的。

叛徒的任务是改变一个已经记账成功的区块。

1763713085384.png

假设每个人单位时间内算计为1,坏人需要在3个好人记录为区块2时完成区块1.1,2.1才能让新加入的参与者以坏人链为开始。

好人完成区块2需要的算力:3

坏人超过区块2需要的算力:6(3个算力负责完成1.1,3个算力负责完成2.1)+1

坏人如果想要篡改一个区块,则知道需要2*好人数+1的算力

篡改并不意味着不可能,只是至少需要掌握2*好人数+1的算力(成本高)

篡改一个的成本如此,如果想要篡改更早的区块,难度几乎不可能,比特币白皮书中关于改账成本的估算:

1763714740053.png

  • 记账成本

如果采用pow模式记账,那对于一个旷工来说记账成功需要成本是多少?

计算成本:求出hash值需要大量的本地计算(本次计算都发生在本地,只要有一个旷工为区块内求出hash值),只需要此随机值广播的网络

**网络传输成本:**计算出hash值的旷工需把hash值广播到互联网,不需要关注接受者和接收情况,也不用关心其他旷工是否认可本结果。

**出错概率与容错:**由于网络原因,可能会存在部分旷工错认为一部分已经过时的节点为最新节点。但当其正常接入互联网后,会立刻发现最新的链,并连接上新链。

总体看来记账成本为固定一个区块的算计成本,不会由于接入旷工的多少而影响记账成本。

准确性为可能由于网络原因,导致新记录的部分区块账务不准确,但随着区块的增多,出错概率越来越小。且篡改可能几乎为0

权益证明(Proof of Stake)POS

参与者抵押自己的资金

  1. 参与者抵押的虚拟货币将被锁定
  2. 随机选择一个验证者提交新的区块
  3. 其他的验证者投票选择接受或者拒绝
  4. 诚实的行为(多数派)获得奖励,篡改者将会受到惩罚(减少一部分抵押的虚拟货币)

与pow相比,记账成功的确定被提前了,一个区块只要大多数验证者确认后。如果攻击者想要篡改他,需要的成本将高的无法承受。

区块链三难问题

  • 安全性:抵抗攻击和审查的能力
  • 可扩展性:高交易吞吐量
  • 去中心化:没有单一节点

比特币选择了安全性和去中心化,而不是扩展性。由于篡改成本过高,保留安全性,由于是完全去中心化的设计,不会由于单个节点的下线导致账务数据丢失。

不可扩展(个人认为不具备伸缩性):由于理论单个区块记账的算力是固定的,不会由于增了多台机器到让一个区块的记账成本变低。每台机器的添加都需要经过区块计算(只是计算出的概率高低问题)。

从极端场景考虑,每个区块记录的账务数量固定,单位算力算出的区块固定,如果交易量过大,需要等待多个区块后才能加入记账。区块的产生速度不会随着交易数量的变多而变快。

加密方式

区块链主要依赖三种关键的密码学工具,来创建一个不可篡改且可验证的系统。

旷工得出不可篡改的结果,其他旷工验证此结果的正确性。

哈希函数(数据连贯性)

hash函数可以把任何输入,转换成固定大小额输出。

  • 确定性:相同的输入总是会产生相同的输出
  • 不可逆性:该函数在一个方向上易于计算,但反方向计算几乎不可能。给定一个hash值,无法轻易的找到原始值。(需要暴力破解和查表)简称二极管
  • 雪崩效应:输入的微小变化会导致完全不同的输出hashCode
1
2
SHA-256("Hello") = 185f8db32271fe25f561a6fc938b2e264306ec304eda518007d1764826381969
SHA-256("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824

区块链中,hash值用来确保数据的完整性。每个区块都包含前一个区块的hash值,从而形成一个不可破坏的链条。

一个记录了上一个节点的hash值的双向链表,如果想要修改链中的某个节点。后续所有的节点都需要做出修改。从实现难度上讲,这几乎是不可能的

数字签名(保证数据真实)

传统金融的身份验证依赖于共享的秘密信息(密码、声纹,指纹等都需要统一且值得信赖的第三方)。但区块链在没有可信机构或安全渠道来共享秘密信息的情况下运行。

数字签名使用了非对称加密技术:在一个方向上计算很容易,但几乎不可能反向计算。当创建一个数字签名时,会生成两个数学相关的数字,一个私钥,一个公钥,私钥必须保密,而公钥可以自由共享。

加密步骤:

  1. 签名创建:私钥为交易创建数字签名。
  2. 签名是私钥和交易内容的唯一组合。
  3. 签名验证:任何人都可以使用公钥来验证,但签名只能由拥有相应私钥的人创建。
  4. 防盗:没有私钥的人,即使拥有之前所有私钥产生的签名,也无法算出新的有效签名。
  5. 防重放:每个签名都必须包含一段唯一的随机数,以确保每个签名都是唯一的。

默克尔树Merkle Tree(数据存储)

1763966497460.png

每个叶子节点表示一笔交易,每个父节点都包含两个叶子节点的hash值。需要验证某个叶子节点,对于其他的节点,只需要保存其他兄弟节点的hash值,和需要验证节点的真实值。

图中展示了验证交易3,只需要下载交易3的值和路径上的hash值即可。由于层层hash,保证了整体数据的完整性。

每次做数据验证,只需要按照层高来验证数据。

总结

  • hash确保历史数据无法被篡改。

  • 数字签名保证数据真实

  • 默克尔树使无需下载大量数据即可验证复杂的数据。

区块链的演变

上面是区块链的基础知识,可目前区块链已经是一个可编程区块链平台。

比特币(Bitcoin)

比特币的创建目的:创建一种无需银行或政府运作的数字货币。

共识机制

比特币使用了中本聪的原始工作量证明(POW Proof of Work)实现。旷工们竞争寻找一个随机数(nonce),当与区块数据一起hash时,会生成一个以特定数量的0开头的hash值。

网络每隔2016个区块(大约两周)会自动调整难度,以保持平均区块时间为10分钟。

如果区块生成速度快:会导致网络分裂,大量旷工在不同的区块版本上工作。

如果区块慢:交易速度会随着变慢。(确保一笔交易成功,最好等主链向后足够长)

UTXO模型(资金管理)

比特币不像银行那样追踪账户余额。它通过UTXO(未花费交易输出)来追踪单个“币”。(类似于实体现金)

1763967976150.png

Alice的初始拥有来自三个不同人的BTC。Alice共拥有1.6BTC,但并没有一个账户存储这个数字。相反,区块链记录了Alice可以使用三个独立的UTXO。

现在Alice想要给Eve发送1.0BTC:

由于Alice没有单个UTXO的余额达到1.0BTC,所以他选择了UTXO#1和#3共计1.3BTC来进行这笔交易。

1763968497019.png

交易如图所示来表示,这里为了简单,按照UTXO的图表示变化。

1763969586890.png

Alice的UTXO#1和#3被使用,此时Eve得到来自Alice的1.0BTC(UTXO#4),Alice得到来自Alice的0.3BTC(UTXO#5)。

1763969876745.png

两人最终钱包的变化情况,本次交易和上图一起完成,实现了一笔交易。

  • UTXO(交易输出)的优点
  1. 并行处理:每个UTXO只能被花费一次,不同的UTXO的交易不会发生冲突。旷工可以同时验证数千笔交易,只要每笔交易引用的UTXO不同,就无需担心重复消费。
  2. 隐私:没有一个全局账户显示总余额。你的BTC分散在多个UTXO中,很难获取到你名下共有多少BTC。
  3. 简单验证:每笔交易可以通过独立验证输入的UTXO是否存在且未被花费,以及数字签名的有效性来完成。无需维护账户状态,也无需担心交易顺序对余额的影响。
  4. 原子操作:交易要么全部完成,要么完全失败。(消耗的UTXO和新产生的UTXO一起)不存在资金被扣除但未转移的情况。

以太坊(Ethereum)

如果区块链可以转账,是否可以运行程序?

第一个通用的区块链计算机。

共识机制

采用权益证明(POS Proof of work),以抵押担保方式来承诺支付。

  • 数学极限:大约13分钟后,交易在数学上变得不可逆。
  • 能源节省:不再需要大量的算力算出随机数。
  • 未来升级:权益证明支持分片,将网络分成并行链来提高吞吐量。(可以增加单位时间内记账成功的交易数量)

账务模型

采用了基于账户的余额系统取代了BTC的UTXO系统。

  • 智能合约:驻留在链上的程序,能够维护自己的状态
  • 外部账户:类似于BTC地址的用户控制账户
  • 合约间调用:每个智能合约可以无缝地相互交互

以太坊的账户主要分两类:

外部拥有账户(EOA):有用户私钥控制,类似于BTC的地址。它们有余额并可以发送交易。

合约账户:由代码控制,而非私钥。它们既有余额,也存储可执行代码和持久数据。

智能合约是驻留在区块链上的自治程序,能够维护自己的状态,并可以被其他账户调用。

这些都依赖于以太坊虚拟机(EVM),它运行在每个节点上,使区块链具有可编程性。EVM定义了可以运行的程序、它们的执行方式以及它们消耗的资源。

索拉那(Solana)

以太坊的升级版

共识机制

solana在现有的POS权益证明机制上,增加了历史证明(Proof of History)。solana不需要等待事件发生时间的共识,而是创建一个加密时钟,在共识前为所有交易加上时间戳,使验证者可以并行处理交易,因为他们已经知道正确的顺序。

Solana每400毫秒生成一个区块,而以太坊需要12秒。BTC需要10分钟。

Solana虚拟机(SVM)

EVM按顺序处理交易,因为智能合约共享全局状态,一个合约修改共享数据,所有其他的交易必须等待。

Solana的设计:

  • 无状态程序:以太坊智能合约内部存储数据,Solana的程序无状态。所有数据存储在独立的账户中,程序从中读取和写入。这种分离使得并行处理成为可能。(多笔交易之间不需要争抢锁)
  • 交易并行处理:Solana的交易必须提前声明将读取和修改哪些账户。运行时可以同时在多个CPU核心上执行不冲突的交易。如果交易A修改账户X,而交易B修改账户Y,它们可以并行运行。(本质上对于单个账户还是串行处理,多个线程操作的账户完全隔离)
  • 优化执行:SVM使用基于寄存器的架构,而不是EVM基于堆栈的方式,从而减少了计算过程中的数据复制和移动,程序编译为本地汇编,而不是字节码,消除了编译的开销。(有点牵强)
  • 可预测的成本:和以太坊多年前确定的固定Gas价格不同,Solana使用了动态费用市场,交易成本反映了实际的网络需求和消耗的计算资源。

Solana简介

账户

存储在区块链上的数据容器。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
pub struct Account {
    /// lamports in the account
      /// 该账户的lamports
    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
  /// 该账户将要在下一个 epoch 支付租金的 epoch
    pub rent_epoch: Epoch,
}

每个账户都拥有一个唯一的32字节地址(如:14grJpemFaf88c8tiVb77W7TYg2W3ir6pfkKz3YjhhZ5)。此地址是账户在区块链上的标识符,用来定位特定的数据。

每个账户可以存储10M的数据,这些数据为可执行的程序代码或者特定程序的数据。

所有账户都需要根据其数据大小存入一定数量的lamport押金以达到“免租”状态。

每个账户都由一个程序拥有,只有拥有程序可以修改账户的数据或提取其lamport。

账户类型

系统账户:存储lamports(SOL的最小单位)并由系统程序拥有。这些账户是基本的钱包账户,用户可以直接与其交互来发送和接收SOL。

代币账户:用来存储SPL代币信息,包含所有权和代币元数据。这些账户由代币程序拥有,并管理Solana生态系统中的所有代币相关操作。代币账户属于数据账户

数据账户:存储特定应用程序的信息,并由自定义程序拥有,这些账户保存应用程序的状态。

程序账户:包含在Solana上运行的可执行代码,也就是只能合约所在的位置。这些账户被标记为executable: true,存储处理指令和管理状态的程序逻辑。

使用账户数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#[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的交易是原子操作,可以有多个指令,要么全部成功,要么全部失败。

一笔交易包含:

  1. 指令:要执行的单个操作
  2. 账户:每个指令要读取或写入特定的账户
  3. 签名者:授权交易的账户
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Transaction {
    instructions: [
        // Instruction 1: Transfer SOL,指令1:转账
        system_program::transfer(from_wallet, to_wallet, amount),
        
        // Instruction 2: Update user profile ,指令2更新用户资料 
        my_program::update_profile(user_account, new_name),
        
        // Instruction 3: Log activity,指令3:记录本次交易日志
        my_program::log_activity(activity_account, "transfer", amount),
    ],
  /// 账户,操作的账户,来源账户,目标账户,用户资料账户,日志记录账户
    accounts: [from_wallet, to_wallet, user_account, activity_account]
  ///授权的账户
    signers: [user_keypair],
}

交易要求和费用

每次交易总大小最大为1232字节,限制了可以执行的指令和账户的数量。

每个指令都需要包含:要调用的程序地址、指令将读取和写入的所有账户,以及任何附加的数据。

指令会按照交易中指定的顺序依次执行。

手续费:每笔交易需每个签名5000lamports的基础费用。(用来补偿验证者处理交易)

可以额外支付优先费用,来提交本次交易的验证的优先级。

Solana上的程序

Solana上的程序本身是无状态的,这意味着它们在函数调用之间不维护任何内部状态。他们接收账户作为输入,然后把处理后的结果写入到账户中。

程序代码保存在 标记为executable: true的特殊账户中,其中包含了在调用执行时的已经编译的二进制代码。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计