什么是叔块
叔块是以太坊引进的,顾名思义叔块就是跟自己的父区块指向的父区块相同。
当矿工打包的时候发现有这样的块存在,就把它打包进去。那么这个块就是当前块的叔叔了,就称为叔块,这个起名还是蛮有意思的。
如上图说是,当打包102的时候,发现还有个黄色的101也指向自己(102)的爷爷(100),那么黄色的101就是一个叔块(当然这个是最高级别的叔块,如果打包102的时候还没有黄色的101,打包103的时候才发现黄色的101,那么黄色的101也是会当做区块打包到103内,区别是黄色101区块的生产者获得奖励不同)
为什么要有叔块
比特币里面是没有叔块概念的,叔块是以太坊中引进的,至于为什么要引进叔块的概念,是与以太坊的缩短出块时间有关。
比特币平均出块时间间隔为10分钟,出现叔块的情况概率比较小,当时中本聪设定的这种情况的叔块是做无用功,不会有任何奖励。
但是以太坊为了缩短出块时间到10s出头,那么叔块产生的概率就比较高了,如果类似比特币的设计,会有很多矿工因为生产了叔块而获取不到任何奖励,矿工的积极性会降低,不利于以太坊生态发展,所以V神引入了叔块的概念,这种情况下矿工打包叔块进区块,叔块生产者和打包叔块的矿工都会有一定的奖励。
可以看下V神的这篇文章:https://blog.ethereum.org/2014/07/11/toward-a-12-second-block-time/
叔块的相关规定
看以太坊的wiki:
如果要打包叔块到区块B的话要满足几点:
1 叔块必须是B的第K层祖先,2<= k <= 7
2 叔块不能是B的祖先
3 叔块必须有合法的block header,但是不必是之前验证过的完整的block(没太看懂)
4 叔块必须没有被包进区块过的
叔块的奖励
生成family和ancestors
在打包生成区块的时候,先计算下叔块的family和ancestors
for _, ancestor := range self.chain.GetBlocksFromHash(parent.Hash(), 7) { for _, uncle := range ancestor.Uncles() { work.family.Add(uncle.Hash()) } work.family.Add(ancestor.Hash()) work.ancestors.Add(ancestor.Hash()) }
目的是后续为了验证叔块的合法性
1 拿到当前blockchain中的最新7个块
2 把这7个块以及块里的uncles加入到family map
3 把这7个块加入到ancestors map
打包叔块进区块
for hash, uncle := range self.possibleUncles { if len(uncles) == 2 { break } if err := self.commitUncle(work, uncle.Header()); err != nil { log.Trace("Bad uncle found and will be removed", "hash", hash) log.Trace(fmt.Sprint(uncle)) badUncles = append(badUncles, hash) } else { log.Debug("Committing new uncle to block", "hash", hash) uncles = append(uncles, uncle.Header()) } }
func (self *worker) commitUncle(work *Work, uncle *types.Header) error { hash := uncle.Hash() if work.uncles.Has(hash) { return fmt.Errorf("uncle not unique") } if !work.ancestors.Has(uncle.ParentHash) { return fmt.Errorf("uncle's parent unknown (%x)", uncle.ParentHash[0:4]) } if work.family.Has(hash) { return fmt.Errorf("uncle already in family (%x)", hash) } work.uncles.Add(uncle.Hash()) return nil }
1 遍历possibleUncles map,这个里面保存着所有的可能的叔块,只有被打包进区块才能成为真正的区块
2 commitUncle里面
a: 如果已经被加入到uncle了,return
b: 如果ancestors map内不包含当前uncle的父区块,return
c: 如果当前family包含该区块,return
最终添加通过的区块到uncle,画个图看下:
从possibleUncles中筛选到最终的uncle的时候要符合possibleUncles中的祖先要在ancestors map中,
所以最终只有101-106这6种情况会被打包到区块107中。
3 最多打包两个uncle到区块中
计算区块/叔块奖励
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { // Select the correct block reward based on chain progression blockReward := FrontierBlockReward if config.IsByzantium(header.Number) { blockReward = ByzantiumBlockReward } // Accumulate the rewards for the miner and any included uncles reward := new(big.Int).Set(blockReward) r := new(big.Int) for _, uncle := range uncles { r.Add(uncle.Number, big8) r.Sub(r, header.Number) r.Mul(r, blockReward) r.Div(r, big8) state.AddBalance(uncle.Coinbase, r) r.Div(blockReward, big32) reward.Add(reward, r) } state.AddBalance(header.Coinbase, reward) }
1 先设置基本的奖励blockReward,默认为5Eth,但是Byzantium版本之后为3Eth,也就是现在的区块奖励为3Eth了
2 计算叔块奖励的公式:
((uncleNumer + 8) - headerNumber)*blockReward/8
那么对于打包区块的时候叔块与当前number的差值奖励是不一样的
因为刚开始就定义了ancestors,就是uncle的祖先只能为当前区块以及前7个区块,那么uncleNumber的取值为当前块(不是当前打包的块,是已经加到blockchain的块)以及前6个块
这个图再贴过来吧。比如打包的块107,那么headerNum是107,uncleNumber的取值范围是101-106,所以最终叔块的奖励范围是:
(2/8~7/8)*blockReward,表格看下:
间隔层数 | 报酬比例 | 报酬(Eth) |
---|---|---|
1 | 7/8 | 2.625 |
2 | 6/8 | 2.25 |
3 | 5/8 | 1.875 |
4 | 4/8 | 1.5 |
5 | 3/8 | 1.125 |
6 | 2/8 | 0.75 |
3 计算区块生产者的奖励,这个矿工打包叔块的原因,是有Eth奖励的
blockReward/32
这是打包一个叔块的奖励:3Eth*/32,如果打包了两个叔块奖励是:3/16Eth