8105 words
41 minutes
区块链学习笔记「2」
2025-11-18
统计加载中...

前言#

BTC 视频就到上一个帖子就结束了,下面是以太坊的时代了,会根据视频加自己搜到的继续写笔记。

ETH-账户#

BTC 系统是基于交易的账本,系统中并未显示记录账户有多少钱,只能通过 UTXO 进行推算。但实际中,使用起来较为别扭。 A 转给 B 钱的时候,需要说明币的来源。实际中只需要存钱说明来源,花钱则不用。此外,账户中的钱在花的时候,必须一次性全部花出去。

以太坊系统则采用了基于账户的模型,与现实中银行账户相似。系统中显示记录每个账户以太币的数量,转账是否合法只需要查看转账者账户中以太币是否足够即可,同时也不需要每次全部转账。同时,这也也天然地防范了双花攻击。 当然,以太坊发这种模式也存在缺点,这种模式存在重放攻击的缺陷。A 向 B 转账,过一段时间,B 将 A 的交易重新发布,从而导致 A 账户被扣钱两次。

为了防范重放攻击,给账户交易添加计数器记录该账户交易过多少次,转账时候将转账次数计入交易的内容中。 系统中全节点维护账户余额和该计数器的交易数,从而防止本地篡改余额或进行重放攻击。

以太坊系统中存在两类账户:外部账户和合约账户。

  • 外部账户:类似于 BTC 系统中公私钥对。存在账户余额 balance 和计数器 nonce
  • 合约账户:并非通过公私钥对控制。(不能主动发起交易,只能接收到外部账户调用后才能发起交易或调用其他合约账户)其除了 balance 和 nonce 之外还有 code(代码)、storage(相关状态-存储)

创建合约时候会返回一个地址,就可以对其调用。调用过程中,代码不变但状态会发生改变。

为什么要做以太坊,换基于账户的模型? 比特币中支持每次更换账户,但以太坊是为了支持智能合约,而合约签订双方是需要明确且较少变化的。尤其是对于合约账户来说,需要保持稳定状态。

ETH-数据结构#

在以太坊中,账户地址为 160 字节,表示为 40 个 16 进制数额。状态包含了余额(balance)、交易次数(nonce),合约账户中还包含了 code(代码)、存储(stroge)。

直观地来看,其本质上为 Key-value 键值对,所以直观想法便用哈希表实现。若不考虑哈希碰撞,查询直接为常数级别的查询效率。但采用哈希表,难以提供Merkle树,下面我们正式开始

一个简单的数据结构——trie(字典树、前缀树)#

如下为一个通过 5 个单词组成的 trie 数据结构(只画出 key,未画出 value)

(ROOT)
|
G
|
+--- E
| |
| N
| |
| E
| |
| +--- R
| | |
| | A
| | |
| | L **[General]**
| |
| +--- S
| |
| I
| |
| S **[Genesis]**
|
+--- O **[Go]**
|
+--- D **[God]**
|
+--- O
|
D **[Good]**

特点:

  1. trie 中每个节点的分支数目取决于 Key 值中每个元素的取值范围(图例中最多 26 个英文字母分叉+一个结束标志位)。
  2. trie 查找效率取决于 key 的长度。实际应用中(以太坊地址长度为 160byte)。
  3. 理论上哈希会出现碰撞,而 trie 上面不会发生碰撞。
  4. 给定输入,无论如何顺序插入,构造的 trie 都是一样的。
  5. 更新操作局部性较好 那么 trie 有缺点吗?当然有: trie 的存储浪费。很多节点只存储一个 key,但其“儿子”只有一个,过于浪费。因此,为了解决这一问题,我们引入 Patricia tree/trie

Patricia trie

Patricia trie 就是进行了路径压缩的 trie。如上图例子,进行路径压缩后如下图所示:

(ROOT)
|
G
|
+--- E
| |
| EN
| |
| +--- E --- ERAL **[General]**
| |
| +--- S --- SIS **[Genesis]**
|
+--- O **[Go]**
|
+--- D **[God]**
|
+--- O --- D **[Good]**

需要注意的是,如果新插入单词,原本压缩的路径可能需要扩展开来。那么,需要考虑什么情况下路径压缩效果较好?树中插入的键值分布较为稀疏的情况下,可见路径压缩效果较好。

在以太坊中,160 位的地址存在有21602^{160}种可能性,和账户数目相比,可以认为地址这一键值非常稀疏。因此,我们可以在以太坊账户管理种使用 Patricia tree 这一数据结构!但实际上,在以太坊种使用的并非简单的 PT(Patricia tree),而是 MPT(Merkle Patricia tree)

MPT#

这个结构的图太复杂了 xd,我自己画不出来

MPT 树的节点有以下 4 种类型:

  1. 扩展节点(Extension Node):只能有一个子节点。
  2. 分支节点(Branch Node):可以有多个节点。
  3. 叶子节点(Leaf Node):没有子节点。
  4. 空节点:空字符串。

图我感觉你需要这个

image

每次发布新区块,状态树中部分节点状态会改变。但改变并非在原地修改,而是新建一些分支,保留原本状态。如下图中,仅仅有新发生改变的节点才需要修改,其他未修改节点直接指向前一个区块中的对应节点。 所以,系统中全节点并非维护一棵 MPT,而是每次发布新区块都要新建 MPT。只不过大部分节点共享。

为什么要保存原本状态?为何不直接修改? 为了便于回滚。如果产生分叉,而后上面节点胜出,那么,下面节点中状态的修改便需要进行回滚。因此,需要维护这些历史记录。

通过代码看以太坊中的数据结构#

  • Block Header 区块头是区块的精髓,它包含了整个区块的元数据,并且用三个特殊的 Merkle Root 将区块的所有信息和全局状态都链接了起来。在 Go-Ethereum(geth)的代码中,它通常被定义为一个结构体

    字段 (Field)类型 (Type)长度 / 大小描述 (Description)
    ParentHashcommon.Hash32 字节父区块的哈希值,用于形成区块链。
    UncleHashcommon.Hash32 字节叔块列表的哈希值 (keccak256(rlp(uncles)))。
    Coinbasecommon.Address20 字节接收区块奖励的矿工地址。
    Rootcommon.Hash32 字节状态树 (State Tree) 的 Merkle 根,代表整个以太坊世界状态的快照。
    TxHashcommon.Hash32 字节交易树 (Transaction Tree) 的 Merkle 根,证明所有交易的完整性。
    ReceiptHashcommon.Hash32 字节收据树 (Receipt Tree) 的 Merkle 根,证明所有交易执行结果的完整性。
    Bloomtypes.Bloom2048 位 (256 字节)布隆过滤器,用于快速检索交易日志。
    Difficulty*big.Int变长当前区块的难度值,用于工作量证明 (PoW)。
    Number*big.Int变长区块高度(从 0 开始)。
    GasLimituint648 字节区块内所有交易允许的最大 Gas 总量。
    GasUseduint648 字节区块内所有交易实际消耗的 Gas 总量。
    Timeuint648 字节区块创建的时间戳 (Unix 时间)。
    Extra[]byte最多 32 字节矿工可以添加的额外数据。
    MixDigestcommon.Hash32 字节工作量证明的 Mix Hash。
    Noncetypes.BlockNonce8 字节工作量证明的 Nonce 值。
  • 区块(Block) 一个完整的区块(types.Block)包含区块头、交易列表和叔块列表。

Block=Header,[Transaction1,...,Transactionsn],[UncleHeader1,...,UncleHeadern]Block={Header,[Transaction_1,...,Transactions_n],[UncleHeader_1,...,UncleHeader_n]}

Transactions中包含发送方、接收方、Gas、Nonce、签名等。 Uncles是叔块头列表。它们是几乎同时挖出的有效但未被选为主链的区块的头部,用于激励矿工并减少中心化。

交易树和收据树#

每次发布一个区块时,区块中的交易会形成一颗 Merkle Tree,即交易树。此外,以太坊还添加了一个收据树,每个交易执行完之后形成一个收据,记录交易相关信息。也就是说,交易树和收据树上的节点是一一对应的。 由于以太坊智能合约执行较为复杂,通过增加收据树,便于快速查询执行结果。 交易树和收据树都是 MPT,而 BTC 中都采用普通的 MT。 MPT 的好处是支持查找操作,通过键值沿着树进行查找即可。对于状态树,查找键值为账户地址;对于交易树和收据树,查找键值为交易在发布的区块中的序号。

交易树和收据树只将当前区块中的交易组织起来,而状态树将所有账户的状态都包含进去,无论这些账户是否与当前区块中交易有关系。 多个区块状态树共享节点,而交易树和收据树依照区块独立。

交易树和收据树的用途:

  1. 向轻节点提供 Merkle Proof。
  2. 更加复杂的查找操作(例如:查找过去十天的交易;过去十天的众筹事件等)

下面两个表格看看他们俩的结构

  • 交易
字段 (Field)类型 (Type)长度 / 大小描述 (Description)
Nonceuint648 字节发送方账户的交易计数器。防止重放攻击。
GasPrice / GasFeeCap / GasTipCap*big.Int变长支付给矿工的 Gas 价格/费用上限/小费上限(取决于 EIP-1559 类型)。
GasLimituint648 字节该交易允许消耗的最大 Gas 量。
To*common.Address20 字节接收方地址。如果为 nil,则表示这是一个创建合约的交易。
Value*big.Int变长随交易发送的以太币数量(单位:Wei)。
Data / Input[]byte变长交易附带的任意数据(函数调用或合约字节码)。
V, R, S*big.Int变长数字签名的三个分量。
Typeuint81 字节交易类型,例如:0x00 (Legacy), 0x01 (EIP-2930), 0x02 (EIP-1559)。
AccessListtypes.AccessList变长EIP-2930/1559 交易中预声明的地址和存储键列表。
  • 收据
字段 (Field)类型 (Type)长度 / 大小描述 (Description)
Statusuint648 字节交易执行状态:1 表示成功 (Success),0 表示失败 (Failure)。
GasUseduint648 字节交易实际消耗的总 Gas 量。
Bloomtypes.Bloom2048 位 (256 字节)交易专属的布隆过滤器,包含了交易日志数据。
Logs[]*types.Log变长交易执行期间智能合约发出的所有事件日志列表。
PostState[]byte变长(仅用于旧版交易) 交易执行后的状态根哈希。
TxHashcommon.Hash32 字节该收据对应的交易哈希。
ContractAddresscommon.Address20 字节如果是合约创建交易,则为创建的新合约地址。
Typeuint81 字节对应的交易类型。

Bloom filter#

支持较为高效查找某个元素是否在某个集合中 最笨:元素遍历,复杂度为 O(n)——轻节点不能用 方法:给一个大的集合,计算出一个紧凑的“摘要”

Bloom filter 特点:有可能出现误报,但不会出现漏报。 Bloom filter 变种:采用一组哈希函数进行向量映射,有效避免哈希碰撞

如果集合中删除元素该怎么操作? 无法操作。也就是说,简单的 Bloom filter 不支持删除操作。如果想要支持删除操作,需要将记录数不能为 0 和 1,需要修改为一个计数器(需要考虑计数器是否会溢出)。

那么他的作用是什么呢?

每个交易完成后会产生一个收据,收据包含一个 Bloom filter 记录交易类型、地址等信息。在区块 block header 中也包含一个 Bloom filter,其为该区块中所有交易的 Bloom filter 的一个并集。 所以,查找时候先查找块头中的 Bloom filter,如果块头中包含。再查看区块中包含的交易的 Bloom filter,如果存在,再查看交易进行确认;如果不存在,则说明发生了“碰撞”。 好处是通过 Bloom filter 这样一个结构,快速大量过滤掉大量无关区块,从而提高了查找效率。

GHOST 协议#

GHOST 协议并不是以太坊原创,是改进版本

最初版本#

比如说一个链上,一个 block 分开挖到了 ABCD 四个分支,B 后面继续挖到了 E,随着时间推移 B 所在链成为最长合法链,因此 A、C、D 区块都作废,但为了补偿这些区块所属矿工所作的工作,给这些区块一些“补偿”,并称其为”Uncle Block”(叔父区块)。 规定 E 区块在发布时可以将 A、C、D 叔父区块包含进来,A、C、D 叔父区块可以得到出块奖励的 7/8,而为了激励 E 包含叔父区块,规定 E 每包含一个叔父区块可以额外得到 1/32 的出块奖励。为了防止 E 大量包含叔父区块,规定一个区块只能最多包含两个叔父区块,因此 E 在 A、C、D 中最多只能包含两个区块作为自己的出块奖励

假定一个矿工挖出了 B,此时他沿着其所在链继续挖,而他知道 A 是和自己“同辈”,则可以将 A 包含进区块挖矿,若挖矿过程中又听到 C 也是“同辈”,则可以停止挖矿,将 C 包含进来重新组织成一个新区块重新挖矿,实际中,由于挖矿过程的无记忆性,这样并不会降低成功挖到矿的概率。

最初版本缺陷:

  1. 因为叔父区块最多只能包含两个,如图出现 3 个怎么办?
  2. 矿工自私,故意不包含叔父区块,导致叔父区块 7/8 出块奖励没了,而自己仅仅损失 1/32。如果甲、乙两个大型矿池存在竞争关系,那么他们可以采用故意不包含对方的叔父区块,因为这样对自己损失小而对对方损失大。

Ghost 协议新的版本#

对上面例子的补充,F 为 E 后面一个新的区块。因为规定 E 最多只能包含两个叔父区块,所以假定 E 包含了 C 和 D。此时,F 也可以将 A 认为自己的的叔父区块(实际上并非叔父辈的,而是爷爷辈的)。如果继续往下挖,F 后的新区块仍然可以包含 B 同辈的区块(假定 E、F 未包含完)。这样,就有效地解决了上面提到的最初 Ghost 协议版本存在的缺陷。

但这样仍然存在一定的问题。 我们将“叔父”这个概念进行了扩展,但问题在于,“叔父”这一定义隔多少代才好呢? 如下图所示(图来自于牛客一个佬的),M 为该区块链上一个区块,F 为其严格意义上的叔父,E 为其严格意义上的“爷爷辈”。以太坊中规定,如果 M 包含 F 辈区块,则 F 获得 7/8 出块奖励;如果 M 包含 E 辈区块,则 F 获得 6/8 出块奖励,以此类推向前。直到包含 A 辈区块,A 获得 2/8 出块奖励,再往前的“叔父区块”,对于 M 来说就不再认可其为 M 的”叔父”了。 对于 M 来说,无论包含哪个辈分的“叔父”,得到的出块奖励都是 1/32 出块奖励。 也就是说,叔父区块的定义是和当前区块在七代之内有共同祖先才可(合法的叔父只有 6 辈)。

image

这样,就方便了全节点进行记录,此外,也从协议上鼓励一旦出现分叉马上进行合并。

以太坊挖矿#

基本思想#

以太坊中,设计了两个数据集,一大一小。小的为 16MB 的 cache,大的数据集为 1G 的 dataset(DAG)。其关系为,1G 的数据集是通过 16MB 数据集生成而来的。轻节点用小的,矿工挖矿减少重复计算就需要 1GB 的大数据集。

16MB 的小 Cache 数据生成方式与莱特币中生成方式较为类似,这个币就不说了(

  1. 通过 Seed 进行一些运算获得第一个数,之后每个数字都是通过前一个位置的值取哈希获得的。
  2. 先生成一个更大的数组(注:以太坊中这两个数组大小并不固定,因为考虑到计算机内存不断增大,因此该两个数组需要定期增大)

1GB 的 DAG 生成方式

大的数组中每个元素都是从小数组中按照伪随机顺序读取一些元素,方法同莱特币中相同。如第一次读取 A 位置数据,对当前哈希值更新迭代算出下一次读取位置 B,再进行哈希值更新迭代计算出 C 位置元素。如此来回迭代读取 256 次,最终算出一个数作为 DAG 中第一个元素,如此类推,DAG 中每个元素生成方式都依次类推。

伪代码理解以太坊挖矿算法#

目前主要使用的是Ethash算法,的设计目标是抗 ASIC,核心概念是:

  1. Seed (种子): 每 30000 个区块(一个“纪元” Epoch)变化一次,由上一个 Epoch 的区块头哈希决定。
  2. Dataset (数据集/巨型 DAG): 基于 Seed 生成的巨大只读文件,大小随时间线性增长(目前已达数百 GB)。这是 Ethash 内存密集性的体现。
  3. Cache (缓存/小 DAG): 基于 Seed 生成的较小文件,用于轻客户端验证。矿工通常也用它来快速生成 Dataset 中的索引。

下面分开来说

  • 初始化和计算
// 输入:
HeaderHash // 区块头信息的Keccak-256哈希 (不包含Nonce, 包含父哈希, 时间戳等)
Nonce // 矿工尝试的64位随机数
BlockHeight // 区块高度
// 依赖于高度的预计算:
Epoch = floor(BlockHeight / 30000)
Seed = GetSeed(Epoch)
Dataset = GenerateDataset(Seed) // 巨大的DAG文件
Target = GetDifficultyTarget() // 目标难度,区块头中的难度值决定
  • Hashimoto 核心算法 (主要工作量) Hashimoto 算法是 Ethash 的核心,用于将区块头、Nonce 和 Dataset 结合起来,进行工作量证明。
function HASHIMOTO(HeaderHash, Nonce, Dataset):
// 步骤 2.1: 混合输入
MixHash = SHA3-512(HeaderHash, Nonce) // 将区块哈希和Nonce进行Keccak-512哈希
// 步骤 2.2: 迭代计算 (对Dataset的内存读取密集操作)
for i from 0 to 63: // 迭代次数 (默认64次)
// 步骤 2.2.1: 确定Dataset的两个随机索引
Index1 = (MixHash[i % 8]) % Dataset.Size // 基于MixHash的部分内容
Index2 = (MixHash[(i+4) % 8]) % Dataset.Size // 第二个索引,也是随机的
// 步骤 2.2.2: 从Dataset中读取两个数据块 (MixHash的实际工作)
Data1 = Dataset[Index1] // 内存随机访问!
Data2 = Dataset[Index2] // 内存随机访问!
// 步骤 2.2.3: 更新MixHash
MixHash = fnv_hash(MixHash, Data1, Data2) // FNV (Fowler–Noll–Vo) 哈希混合
// FNV是一种非加密哈希,用于快速、均匀地混合数据
// 步骤 2.3: 最终哈希
FinalHash = SHA3-256(MixHash) // 对最终的MixHash进行Keccak-256哈希
return FinalHash, MixHash // 返回最终的哈希和混合数据块
  • 验证步骤(工作量证明检查)
// 步骤 3.1: 运行核心算法
(FinalHash, MixHash) = HASHIMOTO(HeaderHash, Nonce, Dataset)
// 步骤 3.2: 检查工作量是否足够
if (FinalHash < Target):
// 挖矿成功!
return Nonce, FinalHash
else:
// 挖矿失败,尝试下一个 Nonce
return "Failure"

目前以太坊挖矿以 GPU 为主,可见其设计较为成功,这与以太坊设计的挖矿算法(Ethash)所需要的大内存具有很大关系。 1G 的大数组与 128k 相比,差距 8000 多倍,即使是 16MB 与 128K 相比,也大了一百多倍,可见对内存需求的差距很大(况且两个数组大小是会不断增长的)。 当然,以太坊实现 ASIC Resistance 除了挖矿算法设计之外,还存在另外一个原因,即其预期从工作量证明(POW)转向权益证明(POS)

权益证明(POS: Proof of State)#

权益证明:按照所占权益投票进行共识达成,类似于股份制有限共识按照股份多少投票,权益证明不需要挖矿。 而这对于 ASIC 矿机厂商来说,就好比一把悬在头上的达摩克利斯之剑。因为 ASIC 芯片研发周期很长,成本很高,如果以太坊转入权益证明,这些投入的研发费用将全部白费(ASIC 矿机只能用于挖特定的加密货币) 但实际上,以太坊目前仍然是 POW 挖矿共识机制。在设计之初,以太坊开发者就设想要从 POW 转向 POS,并为了防止有矿工不愿意转埋下了一颗“难度炸弹”。但截至目前,以太坊仍然基于 POW 共识机制。

有个很好玩的机制,预挖矿

以太坊中采用的预挖矿的机制。这里“预挖矿”并不挖矿,而是在开发以太坊时,给开发者预留了一部分货币。以太坊的早期开发者,目前就很有钱了。(致富新思路!果然技术=Money 呀) 而比特币并未采用这一模式,所有比特币都是通过挖矿产生的。但早期挖矿难度容易,所有中本聪本人本来就有很多币(但没花…) 和 Pre-Mining 对应,还有 Pre-Sale,Pre-Sale 指的是将预留的货币出售掉用于后续开发,类似于拉风投或众筹。目前,各类加密货币很多,存在一部分货币就在采用 Pre-Sale 来获取资金,如果此时买入,后续如果该货币取得成功,同样可以获得很大收益,但真正成功的货币只占少数,这就是其风险性。

以太坊难度调整#

调整难度算法:

D(H)={D0if Hi=0max(D0,P(H)d+x×ζ2)+ϵotherwiseD(H) = \begin{cases} D_0 & \text{if } H_i = 0 \\ \max(D_0, P(H)_d + x \times \zeta_2) + \epsilon & \text{otherwise} \end{cases}where:D0=131072\text{where:} \quad D_0 = 131072xP(H)d2048x \equiv \left\lfloor \frac{P(H)_d}{2048} \right\rfloorζ2max(yHsP(H)s9,99)\zeta_2 \equiv \max \left( y - \left\lfloor \frac{H_s - P(H)_s}{9} \right\rfloor, -99 \right)

难度调整是根据父区块的难度调整的,y 和父区块中 uncle 的数量有关,如果有 uncle,y=2,反之为 1 上界为-99 是为了应对黑客攻击和其他的黑天鹅事件。

这个时候开发者又想出来一个东西,难度炸弹

为什么要设置难度炸弹? 根据以上以太坊难度调整算法可以看到,该算法可以很好地动态调整挖矿难度,从而保障系统整体出块时间维持在 15s 左右。但之前在挖矿算法的文章中有介绍到,以太坊在设计之初就计划要逐步从 POW(工作量证明)转向 POS(权益证明),而权益证明不需要挖矿。 从旁观者角度来看,挖矿消耗了大量电力、资金等,如果转入放弃挖矿,必然是一件好事。但从矿工的角度,花费了很大精力投入成本购买设备,突然被告知“不挖矿了”,这必然是一件很难接受的事情。而以太坊本身为一个分布式系统,其转入 POS 必须经过系统中大多数矿工认可才行,如果届时矿工联合起来转入 POS,那么这一设计初衷就成了一江流水。 因此,以太坊在设计之初便添加了难度炸弹,迫使矿工转入 POS。那么如何促使矿工自愿升级软件,而非坚持 POW 呢?

可以看到,在以太坊早期时,区块号较小,难度炸弹计算所得值较小,难度调整级别基本上通过难度调整中的自适应难度调整部分决定,而随着越来越多区块被挖出,难度炸弹的威力开始显露出来,这也就使得挖矿变得越来越难,从而迫使矿工愿意转入 POS。

难度炸弹调整#

上面提到,以太坊设想是通过埋设难度炸弹迫使矿工届时愿意转入权益证明,但现实中有一句话:“理想很丰满,现实很骨感”。在实际应用中,权益证明的方式仍然并不成熟,目前以太坊共识机制仍然是 POW,依然需要矿工参与挖矿维护以太坊系统的稳定。也就是说,转入 POS 的时间节点被一再推迟,虽然挖矿变得越来越难,系统出块时间开始逐渐变长,但矿工仍然需要继续挖矿。 在上面难度炸弹的公式中,有人应该注意到了第二项中的 fake block number,该数仅仅为对当前区块编号减去了三百万,也就是相当于将区块编号回退了三百万个。那么,在前三百万个区块的时候,这个 fake block number 就是负数吗? 答案是否定的。实际上,在以太坊最初的设计中,并没有第二个公式。也就是说,最初就是简单地直接用区块编号除以 100000。而在转入权益证明时间节点一再推迟后,以太坊系统采取了将区块编号回退三百万个区块的方法来降低挖矿难度,当然,为了保持公平,也将出块奖励从 5 个以太币减少到了 3 个以太币,这也是 fake block number 这一项出现的原因。 下图显示了难度调整对难度炸弹难度影响的结果:

image

ETH-权益证明#

权益证明#

首先不得不提的是,挖矿太费电了,这些能耗是必须的吗? 矿工挖矿是为了取得出块奖励,获取收益。而系统给予出块奖励的目的是激励矿工参与区块链系统维护,进行记账,而挖矿本质上是看矿工投入资金来决定的(投入资金买设备->设备决定算力->算力比例决定收益)。 那么,为什么不直接拼“钱”呢?现状是用钱购买矿机维护系统稳定,为什么不大家都将钱投入到系统开发和维护中,而根据投入钱的多少来进行收益分配呢?这就是权益证明的基本思想。

一般来说,采用权益证明的货币,会先预留一些货币给开发者,而开发者也会出售一些货币换取开发所需要的资金,在系统进入稳定状态后,每个人都安装持有货币的数量进行投票。 优点:

  1. 省去了挖矿的过程,也避免了因此产生的能耗和对环境影响,减少了温室气体的排放。
  2. 维护区块链安全的资源形成闭环,而 POW 中维护其安全的资源需要通过现实中流通的货币购买矿机等设备进去区块链的,这也就导致只要有人想要攻击,只需要外部聚集足够资金就可以攻击成功(小型币种很容易被攻击,也就是在摇篮里就扼杀掉)。可见,POS 机制可以有效防御这种情况。

有些币种根据持有币的权益进行挖矿难度调整(实际并不能这么简单设置,因为会导致“旱的旱死,涝的涝死”,需要添加一定限制),也就是结合 POW 和 POS。可见,POS 与 POW 并不互斥。

当然,权益证明这么好,为什么实际中并未得到大规模应用呢? 原因是其中仍然存在很多挑战,例如“双边下注”:

区块链系统产生了分叉,存在两个区块 A 和 B 竞争主链时,采用权益证明的方法就是所有持币者对这两个区块投入币进行投票,从而决定哪一个区块成为最长合法链上的区块。假如有一个人,在 A 和 B 同时进行了下注。最终 A 区块胜出,那么他能够获得 A 区块相应收益,而在 B 区块进行投票放入的“筹码”也会被退还,这也就导致其每次都能获得收益。 由于一个人可以拥有多个账户,所以我们无法强迫一个人一次只能投向一个区块。而越有钱的人,通过“双边下注”得到的收益也就越多。

以太坊拟采用的权益证明#

以太坊中,准备采用的权益证明协议为 Casper the Friendly Finality Gadget(FFG),该协议在过渡阶段是要和 POW 结合使用的。 在比特币系统中,我们有提到为了防范分叉攻击,一个交易在其获得 6 次确认(其后跟着 6 个区块)后认为该区块安全。但实际上,这种安全只是概率意义上的安全,仍然可能会被拥有强大算力的用户在其前面发动分叉攻击进行回滚。 Casper 协议引入一个概念:Validator(验证者),一个用户想要成为 Validator,需要上交一笔“保证金”,这笔保证金会被系统锁定。Validator 的职责是推动系统达成共识,投票决定哪一条链成为最长合法链,投票权重取决于保证金数目。 实际中,采用两次投票的方式:预投票和 Commit 投票,规定每次投票结果都要获得 2/3 以上的验证者同意。在实际中,针对其进行了一些修改,两次投票在实际中只需要一次即可。

矿工挖矿会获得出块奖励,而验证者也会得到相应奖励。当然,为了防止验证者的不良行为,规定其被发现时要受到处罚。例如某个验证者“行政不作为”,不参与投票导致系统迟迟无法达成共识,这时扣掉部门保证金;如果某个验证者“乱作为”,给两边都进行投票,被发现后没收全部保证金。没收的保证金被销毁,从而减少系统中货币总量。验证者存在“任期”,在任期结束后,进入“等待期”,在此期间等待其他节点检举揭发是否存在不良行为,若通过等待期,则可以取回保证金并获得一定投票奖励。

Q:这样一定能保证不被篡改吗? 在该协议下,矿工无论算力多么强,最终投票权都不在其手中。必须在系统中,存在大量“验证者”进行了两边投票,也就是说,至少 1/3(该协议规定超过 2/3 才有效)的验证者两侧都投票,才会导致系统被篡改。而这一旦被发现,这 1/3 验证者的保证金将会被没收。

其他观点#

前面的基本观点都是“挖矿消耗大量电能,而这是不好的”,但也有人持有相反观点。 他们认为其所消耗的电能所占比值并不大,而且其对于环境的影响是有限的。挖矿提供了将电能转换为钱的手段,而电能本身难以传输和存储,一般来说,白天所发的电不足,晚上所发的电又多于实际需求。因此,挖矿为将多余的电脑转换为有价值的货币提供了很好的解决手段。 也就是说挖矿消耗电能可以有效消耗过剩产能,带动当地经济发展。

因此可见,世间事物并不是非黑即白的,同样一个事物,从不同角度来看,就会有不同的结论,而这些结论可能是互相对立的。处于世间,我们也应当注意到这一点,跳出自己固有认知,站在其他角度来思考问题,消弥分歧

总结#

这是 b 站视频的大部分内容。ETH 给我感受就是比 BTC 复杂一点,为了避免一些事情而避免,后面会开始sol的学习

区块链学习笔记「2」
https://www.zhuangsanmeng.xyz/posts/blockchain2/
Author
zsm
Published at
2025-11-18
License
MIT

Some information may be outdated