GHOST协议
以太坊系统将出块时间降到了15秒左右,相比于比特币系统大幅度提升了出块的速度,但由于P2P网络延迟,会导致出现大量的临时性分叉,间接造成算力的分散,若沿用比特币系统中最长合法链原则,将导致算力越大的节点发布的区块成为最长合法链的概率越大,造成中心化带来的不成比例的优势(centralization bias)。
以太坊系统中采用基于GHOST协议,核心如下:发布的合法区块即使没有成为最长合法链,同样也会得到7/8的区块奖励,这样的区块成为uncle block,包含uncle block的区块将获得1/32的区块奖励,一个区块最多可包括两个uncle block,但uncle block中的分叉交易不被执行。
uncle block奖励如图所示:
以太坊系统对叔块做了限制,叔块可以隔代,但必须跟当前区块有共同的祖先并在七代以内才可以,并且随着代数的增加,叔块由7/8个区块奖励奖励递减至0。另外,只有分叉后的第一个区块可以得到奖励,不鼓励分叉,降低分叉攻击的可能,原因如下:
- 减轻全节点的存储压力,只记录近几代未包含的叔块即可;
- 鼓劲出现分叉后尽早进行合并,以获取更多的奖励;
与比特币类似,以太坊也存在两种奖励机制:
- block reward
出块奖励 - gas fee
区块中包含智能合约,执行智能合约时得到的费用,奖励相较于区块奖励较低,并且叔块无法得到gas fee。
PoS proof of stake
目前比特币与以太坊的共识都是用的是PoW共识协议,即工作量证明,其最大的缺点是造成了大量资源的浪费,包括矿机的费用及挖矿的能耗。PoS的思路就是将挖矿投入的资金直接进行区块链的开发,通过投入资金的占比决定收益的分配,省去了挖矿的过程,也称为虚拟挖矿(virtual mining)。
另外,PoW可通过区块链外部的资源对区块链自身进行影响,例如购买足够矿机的转换为算力进行攻击,对小型的货币造成致命打击(altcoin infanticide),PoS协议中,只有获取足够多货币份额才能发动攻击,而这些份额是从自身内部获得的,避免外界的资源对系统本身造成直接的影响,完成维护自身安全的一个闭环。
PoS与PoW并不是互斥的,有的加密货币采用两者的混合模型,挖矿的难度与与自身的权益和持有币的数量是相关的,持有越多则难度越小。但由此会造成持有最多货币者每次挖矿难度都为最小的问题,所以还需要有一个货币锁定(proof of deposit)的机制进行支持:为了降低难度而投入的货币,在区块发布后会锁定一段时间。
Casper FFG
ETH准备采用的PoS协议为Casper the Friendly Finality Gadgest(Casper FFG),在过渡阶段也需要混合 PoW共同使用,为工作量证明提供Finality。单纯的基于PoW的交易会被回滚取消,而Finality是一种最终的状态,包含在Finality中的交易不会被取消。
Casper引入Validator验证者的概念,成为Validator需要锁定一定数量的保证金,其职责是推动系统达成共识,投票决定最长合法连,投票的权重取决于保证金的数量大小。
系统中每挖出50个区块作为一个epoch,需要投票决定其能够否成为Finality,每轮投票必须得到2/3以上Validator认可才能通过(按保证金比例计算),如图所示,只有前一个epoch和与当前epoch都投票通过,当前的epoch才算有效。
Validator通过验证工作可得到相应的奖励,若Validator有不良的行为,则回会到相应的处罚,例如扣除其保证金。
以太坊系统的设想是逐步从PoW过渡到PoS,随着时间推移,挖矿得到的奖励越来越少,权益证明得到的奖励越来越多,最终完全使用权益证明。
挖矿难度调整
区块难度公式如下:
D
(
H
)
≡
{
D
0
,
H
i
=
0
m
a
x
(
D
0
,
P
(
H
)
H
d
+
x
×
ς
2
)
+
ϵ
,
Y
=
f
(
X
)
D(H)\equiv \begin{cases} D_{0}, \quad &H_{i}=0\\ max(D_{0},P(H)_{H_{d}}+x\times \varsigma_{2})+\epsilon,\quad &Y=f(X) \end{cases}
D(H)≡{D0,max(D0,P(H)Hd+x×ς2)+ϵ,Hi=0Y=f(X)
- D 0 ≡ 131072 D_{0}\equiv131072 D0≡131072,保证了挖矿的最低难度
- D ( H ) D(H) D(H)为本当前区块的难度,由基础部分 P ( H ) H d + x × ς 2 P(H)_{H_{d}}+x\times \varsigma_{2} P(H)Hd+x×ς2和难度炸弹 ϵ \epsilon ϵ 部分组成,其中难度炸弹部分功能是向PoS协议过渡
- P ( H ) H d P(H)_{H_{d}} P(H)Hd为父区块(当前区块链的最后一个区块)的难度,每个区块的难度都是在父区块难度基础上进行调整
- x × ς 2 x\times \varsigma_{2} x×ς2自适应调节区块难度,维持稳定的出块速度
- x ≡ [ P ( H ) H d 2048 ] x\equiv \left[\frac{P(H)_{H_{d}}}{2048}\right] x≡[2048P(H)Hd], x x x为调整的单位
- ς 2 ≡ m a x ( y − [ H s − P ( H ) H s 9 ] , − 99 ) \varsigma_{2} \equiv max\left(y-\left [ \frac{H_{s}-P(H)_{H_{s}}}{9}\right], -99\right) ς2≡max(y−[9Hs−P(H)Hs],−99), ς 2 \varsigma_{2} ς2为调整的系数,正数为上调难度,负数为下调难度,与出块时间和是否有叔块 y y y有关,-99为难度降低的上限值;,一次性下调难度最大为99/2048,防止系统被异常攻击
- y y y取值由是否有叔块有关,如果有叔块则为2,如果没有叔块则为1,以维持系统货币发行总供应量稳定
- H s H_{s} Hs为当前区块的时间戳, P ( H ) H s P(H)_{H_{s}} P(H)Hs为父区块的时间戳,出块时间基于9进行调整
-
ς
2
≡
[
2
[
H
i
÷
100000
]
−
2
]
\varsigma_{2}\equiv\left[2 ^{ \left [ H_{i}\div100000\right]-2}\right]
ς2≡[2[Hi÷100000]−2],呈指数形式增长,设置此项的原因是要降低PoW协议迁移到PoS协议发生fork风险,因为后期挖矿难度指数增长,矿工更倾向于基于PoS协议工作
智能合约
智能合约是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。智能合约的账户保存了合约当前的运行状态,包括如下:
- balance 当前余额
- nonce 交易次数
- code 合约代码
- storage 存储,数据结构为MPT
ETH智能合约一般用Solidity语言编写。
调用智能合约与转账类似,实际上是发起了对合约的调用,调用函数及参数在data域中说明:
合约间可以相互调用,但合约账户不能主动发起一次交易,所有的交易只能由外部账户发起。
智能合约创建与运行
- 编译合约
智能合约代码写完后,编译成bytecode; - 创建合约
外部账户发起转账交易到0x0的地址,转账金额为0,合约bytecode放于data域中; - 运行合约
智能合约运行于EVM(Ethereum Virtual Machine)上;
gas fee
执行合约中的指令要收取汽油费,有发起交易方进行支付,交易的数据结构如下:
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
- AccountNonce:交易序号,防止重放攻击;
- Price:单位汽油的价格;
- GasLimit:交易中愿意支付的最大汽油量;
- Recipient:收款人的地址;
- Amount:转账金额;
- Payload:data域,调用的函数及参数;
不同的指令消耗的gas fee不同。全节点收到一个对智能合约调用的交易进行同步时,通过Price和GasLimit计算出可能花掉的最大gas fee,然后将此费用从账户中扣除,根据实际情况计算实际的费用后,将多余费用的退回,若费用不够则进行错误处理。
错误处理
ETH中的所有交易执行具有原子性,所以在运行智能合约出现错误,会导致整个交易回滚至开始执行之前的状态。
发生错误的交易也要记录在区块中,用于记录gas fee的变更。所以发布到区块链上的交易不一定都是成功执行的。
智能合约调用接口
智能合约的执行必须时确定性的,由此全节点在运行交易时才能得到相同的结果以达成共识,所以智能合约的变成语言不能像通用的编程语言通过系统调用获取环境信息,因为每个全节点的执行环境并不相同,所以必须通过固定的变量值获取状态信息。智能合约可获得的区块信息如下:
// 给定区块的哈希,仅对最近的256个区块有效而不包括当前区块
block.blockhash(uint blockNumber) returns (bytes32)
// 挖出当前区块的矿工地址
block.coinbase(address)
// 当前区块难度
blcok.difficulty(uint)
// 当前区块gas限额
block.gaslimit(uint)
// 当前区块号
block.number(uint)
// 自uint epoch起始到当前区块以秒计的时间戳
block.timestamp(uint)
智能合约可获得的调用信息如下:
// data域,调用的函数和参数取值
msg.data(bytes)
// 当前调用剩余gas
msg.gas(uint)
// 消息发送者
msg.sender(address)
// data域前4个字节,为函数标识符
msg.sig(bytes4)
// 随交易发送的Wei的数量
msg.value(uint)
// 当前区块的时间戳
now(uint)
// 交易的gas价格
tx.gasprice(uint)
// 交易的最开始发起者
tx.origin(address)
智能合约中地址类型如下:
// 成员变量,地址类型的余额
<address>.balance(uint256)
// 向address转账amount,发送2300gas不可调节
<address>.transfer(uint256 amount)
// 向address转账amount,发送2300gas不可调节
<address>.send(uint256 amount) return (bool)
// 发动函数调用,也可以进行转账,将剩下的所有汽油费全部发送过去
<address>.send(uint256 amount) return (bool)
Code is Law
智能合约的规则是由代码逻辑决定的,代码一旦发布到区块链上就改不了了,优点是没有人能篡改规则,这样的坏处是规则中有漏洞也无法修改,智能合约如果设计的失误,有可能会将货币永久的锁定,j将无法取不。
参考资料
区块链基础系列
欢迎打赏Σ(っ°Д°;)っ