主页 > imtoken支持bcc > 附注 34 - 挖矿奖励

附注 34 - 挖矿奖励

imtoken支持bcc 2023-02-19 05:17:24

矿工的收入来自挖矿奖励。 只有这样,才能鼓励矿工积极参与挖矿,维护网络安全。 那么,以太坊是如何奖励矿工的呢?

本文通过提出两个问题帮助您理解该机制:

奖励是如何计算的; 何时何地颁发奖励; 1. 奖励如何计算

奖励分为三部分:

新区块奖励 叔块奖励 矿工费

{总奖励} = 新区块奖励 + 叔块奖励 + 矿工费

1.1 新区块奖励

是矿工消耗电力和完成工作量证明的奖励。 本次奖励调整了两次:

起初,每个区块奖励 5 ETH; 2017年10月16日(区块高度4370000)实施拜占庭硬分叉,奖励减为3 ETH; 2019 年 2 月 28 日(区块高度 Block height 7280000)执行君士坦丁堡硬分叉,再次将奖励减少到 2 ETH。

以太坊在2015年7月正式发布以太坊主网后,其团队规划了发展阶段,分为“边疆”、“家园”、“大都市”和“宁静”四个阶段。 拜占庭和君士坦丁堡是大都市的两个阶段。

新区块奖励是矿工的主要收入来源,新区块奖励低至 2 Ether。 对矿机厂商和矿工,乃至以太坊的挖矿生态都会产生比较大的影响和调整。 由于挖矿收益减少,机会成本增加,以太坊挖矿的成本效益将低于其他币种,这可能会降低矿工的积极性。 这也是一种助燃剂,迫使以太坊升级到以太坊2.0,迫使以太坊更新。

1.2 叔块奖励

以太坊的平均区块间隔为 12 秒。 区块链软分叉是一种普遍现象。 如果和比特币一样处理,只有最长链上的区块才会有区块奖励。 对于最终没有进入最长链的矿工来说是非常不公平的,这种“不公平”将是一种普遍的情况。 这会影响矿工挖矿的积极性,甚至可能削弱以太坊网络的系统安全性,也是一种算力的浪费。 因此,以太坊系统为不在最长链上的叔块设置了“叔块奖励”。

叔块奖励也分为两部分:

奖励叔块的创造者; 奖励收集叔块的矿工;

叔块创建者的奖励根据“远近”关系不同。 离当前区块越远,奖励越少。

{叔块挖矿奖励} = {8-(当前区块高度-叔块高度)} {8} * {当前区块挖矿奖励}

叔块

按挖矿奖励2 ETH计算

第一代

7/8

1.75 以太币

第二代

6/8

1.5 以太币

第三代

5/8

1.25 以太币

第四代

4/8

1 以太币

第五代

3/8

0.75 以太币

第六代

2/8

0.5 以太币

第七代

比特币挖矿算力_比特币挖矿怎样算收益_rx570挖矿比特币算力

1/8

0.25 以太币

注意:叔块中产生的交易手续费不会返还给创建者。 毕竟叔块里面的交易是不能统计的。

包含叔块的矿工每包含一个叔块,将获得多 1/32 的块挖矿奖励。

收集叔块的奖励 = 数量 \times \frac{新区块奖励}{32}

1.3 矿工费

矿工处理交易,校验和包含在块中。 这时,交易签名者需要向矿工支付矿工费。 每笔交易收取的矿工费取决于交易消耗的gas量,等于用户设置的GasPrice乘以交易消耗的gas。

费用 = \text{tx.gasPrice} \times \text{tx.gasUsed}

2. 何时何地奖励

奖励是当一个区块被挖出并打包时,奖励的分配已经在其中完成,相当于实时结算。

矿工费的分配,在处理一笔交易时比特币挖矿怎样算收益,根据交易消耗的Gas直接存入矿工账户; 区块奖励和叔块奖励在所有交易处理完毕后实时计算。

3.代码展示

3.1 实时结算交易矿工费

//core/state_transition.go
func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
	 //...
	var (
		ret   []byte
		vmerr error // vm errors do not effect consensus and are therefore not assigned to err
	)
	if contractCreation {
		ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
	} else {
		// Increment the nonce for the next transaction
		st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
		ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
	}
	st.refundGas()
	st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
	return &ExecutionResult{
		UsedGas:    st.gasUsed(),
		Err:        vmerr,
		ReturnData: ret,
	}, nil
}

3.2 挖矿奖励和叔块奖励实时结算

//consensus/ethash/consensus.go:572

比特币挖矿怎样算收益_rx570挖矿比特币算力_比特币挖矿算力

func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { // Accumulate any block and uncle rewards and commit the final state root accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) } 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 } if config.IsConstantinople(header.Number) { blockReward = ConstantinopleBlockReward } // 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) }

4.叔块

4.1 什么是叔块

指**未能成为区块链最长链的一部分的区块(废弃区块),但当它们被包含在后续区块中时,这些区块被称为“叔块”。

是针对区块的,是指区块中包含的老祖宗孤块,未能成为区块链最长链的一部分而被包含。

它是当前块的叔块,一个块最多可以记录7个叔块。 叔块也是一个拥有合法数据的块,但是它所在的区块链分支还没有成功成为主链的一部分。

如上图所示,新区块E可以包含两个绿色孤块B和C,但是灰色区块不能包含,因为它们的父区块不在新区块所在的区块链上。 黄色块和红色新块是兄弟块,不能被新块包含为叔块。

4.2 为什么要设计叔块

在比特币中,由于临时分叉(软分叉)而未能成为最长合法链上的区块的区块称为孤儿块,孤儿块没有区块奖励。 研究发现,一个新区块传播到整个比特币网络 95% 的节点需要 12.6 秒。 在比特币系统中,平均每 10 分钟产生一个区块,并且有足够的时间将新区块广播给全网其他节点。 这种临时分叉的概率是相当小的。 根据历史数据,平均3000多个区块会出现一次临时分叉,相当于20多天出现一次这样的临时分叉,属于比较“罕见”的情况。

但是以太坊的出块时间已经缩短到每块 12 到 14 秒。 更短的时间意味着临时分叉的概率大大增加。 这是因为当矿工A挖出一个新区块时,需要向全网广播,广播过程需要时间。 由于以太坊的出块时间较短,其他节点可能在收到矿工A释放的区块之前已经挖出相同高度的区块,造成临时分叉。 在以太坊网络中,临时分叉发生的概率约为 6.6%。

rx570挖矿比特币算力_比特币挖矿怎样算收益_比特币挖矿算力

比特币挖矿算力_rx570挖矿比特币算力_比特币挖矿怎样算收益

以上数据来自(2020-06-03),目前以太坊叔块率为6.6%。 这意味着在以太坊网络中,每 100 个区块大约产生 7 个叔块。 如果按照平均出块时间13.5秒计算,一个小时内大约有17.6个临时分叉。

以太坊系统中的临时分叉是一种普遍现象。 如果和比特币一样处理,只有最长链上的区块才会有区块奖励。 对于矿工来说,这是非常不公平的,这种“不公平”将是一种普遍的情况。 这会影响矿工挖矿的积极性,甚至可能削弱以太坊网络的系统安全性,也是一种算力的浪费。 因此,以太坊系统为不在最长链上的叔块设置叔块奖励。

4.3 如何在区块中包含叔块

当节点不断收到区块比特币挖矿怎样算收益,尤其是多个相同高度的区块时,以太坊会陷入短期的软分叉,或者在多个软分叉分支之间来回切换。 一旦发生软分叉,就意味着一个区块未能成为最长链的一部分。

rx570挖矿比特币算力_比特币挖矿怎样算收益_比特币挖矿算力

比如上图中,挖矿依次收到A、B、C,会在本地验证并存储这些区块,但最终会按照最长链规则切换到分支B。 所以此时A和C暂时成为孤块。 矿工将基于B挖出下一个新的区块D。

比特币挖矿算力_rx570挖矿比特币算力_比特币挖矿怎样算收益

此时,D 可以将临时存储在区块 D 本地的孤立区块 A 和 C 作为其叔块。 当然,D不仅可以收集初代祖宗,还可以收集七代以内的孤块。 但是有一些限制。 以下图中的新区块N为例:

rx570挖矿比特币算力_比特币挖矿怎样算收益_比特币挖矿算力

N不能包含A:因为A不在七代祖中(区间要求); N不能包括M:因为M不是N的祖先,而是兄弟; N不能包含E、G、K、L:因为它们的父块不在N所在块的分支上,但是B可以; N不能同时包含D、C、B:因为一个区块最多可以包含两个叔块,所以选择两个大于三个; 当D被F包含时,N不能重复包含D; N不包括F或H:因为F和H不是孤立的块;

在挖出一个新区块并准备区块头信息时,矿工会从本地节点存储中获取七代以内的所有家族区块,并根据上述规则选择最多两个叔块。 另外,选择时优先考虑本地叔块。

5. 叔块奖励分配

叔块奖励分为两部分:奖励包含叔块的矿工和奖励叔块创建者。

5.1 奖励叔块创建者

叔块创建者的奖励根据“远近”关系不同。 离当前区块越远,奖励越少。

{叔块奖励} = {8-(当前区块高度-叔块高度)}{8} * {当前区块挖矿奖励}

5.2 收集叔块的矿工

矿工为当前新区块的矿工。 除了获得原始区块挖矿奖励(2 ETH)和交易手续费外,他还可以获得包含叔块的奖励。 每包含一个区块,他就会多获得1/32的区块挖矿奖励。

以块 [10192970]() 为例:

比特币挖矿怎样算收益_比特币挖矿算力_rx570挖矿比特币算力

区块矿工2Miners:SOLO共获得2**.**385338652682918613 ETH奖励,其中:

2 ETH为挖矿奖励; 0.322838652682918613 ETH为交易手续费; 0**.**0625 ETH是包含一个叔块的奖励,是2 ETH挖矿奖励的1/32。

包含的[叔块]()为第一代叔块,奖励2个ETH的7/8。

5.3 叔块如何包含在块中

比特币挖矿算力_比特币挖矿怎样算收益_rx570挖矿比特币算力

6.块存储

本文提到的挖矿环节中的存储环节,当矿工通过穷举计算找到符合要求的区块Nonce时,就标志着新区块被成功挖出。

这时,矿工会直接将这个合法的区块存储在本地。 下面具体说明矿工如何将自己挖出的新区块存储在geth中。

比特币挖矿怎样算收益_比特币挖矿算力_rx570挖矿比特币算力

经过上一个链接“PoW 寻找 Nonce”,我们已经有了完整的区块信息。

rx570挖矿比特币算力_比特币挖矿算力_比特币挖矿怎样算收益

在“处理本地交易”和“处理远程交易”之后,你有一个完整的大宗交易收据列表:

rx570挖矿比特币算力_比特币挖矿算力_比特币挖矿怎样算收益

区块中的每一笔交易处理完后,都会有一张交易收据。 本次交易的执行结果信息记录在交易回执中。 对于交易收据,我们在之前的课程中已经进行了说明,这里不再赘述。

同时,“区块奖励发放”后,区块状态不会再发生变化。 这时,我们已经获得了一个可以代表区块的状态数据。 状态`state`会在内存中记录本区块交易执行后状态发送的变化信息,包括新增、变化和删除的数据。

上述的区块(Block)、交易回执(Receipt)、状态(State)就是本次挖矿的产物,本地只需要存储这三部分数据。

rx570挖矿比特币算力_比特币挖矿算力_比特币挖矿怎样算收益

rx570挖矿比特币算力_比特币挖矿算力_比特币挖矿怎样算收益

挖矿中处理和存储这些数据的代码如下:

//miner/worker.go:595
var (
				receipts = make([]*types.Receipt, len(task.receipts))
				logs     []*types.Log
			)
			for i, receipt := range task.receipts {//❶
				// add block location fields
				receipt.BlockHash = hash
				receipt.BlockNumber = block.Number()
				receipt.TransactionIndex = uint(i)
				receipts[i] = new(types.Receipt)
				*receipts[i] = *receipt
				for _, log := range receipt.Logs {
					log.BlockHash = hash
				}
				logs = append(logs, receipt.Logs...)//❷
			}
			// Commit block and state to database. //❸
			_, err := w.chain.WriteBlockWithState(block, receipts, logs, task.state, true)
			if err != nil {
				log.Error("Failed writing block to chain", "err", err)
				continue
			}
			log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash,
				"elapsed", common.PrettyDuration(time.Since(task.createdAt)))

遍历交易回执,为每个交易回执添加当前区块信息(blockHash、BlockNumber、TransactionIndex),这样就可以在本地记录交易回执与区块的查找关系。 同时将交易回执中产生的日志信息提取到一个大集合中,整体存储为一个块日志。 开始向本地数据库提交区块(Block)、交易回执(Receipt)、状态(State)和日志(log)。

在 `writeBlockWithState` 中,所有数据都作为批量事务写入数据库:

blockBatch := bc.db.NewBatch()
	rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd)
	rawdb.WriteBlock(blockBatch, block)
	rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts)
	rawdb.WritePreimages(blockBatch, state.Preimages())
	if err := blockBatch.Write(); err != nil {
		log.Crit("Failed to write block into disk", "err", err)
	}

比特币挖矿怎样算收益_比特币挖矿算力_rx570挖矿比特币算力

// Commit all cached state changes into underlying memory database. root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number())) //... // Set new head. if status == CanonStatTy { bc.writeHeadBlock(block) }

在一次交易中,区块难度、区块、交易回执、Preimages(key mapping)分别写入数据库,最后提交状态。

那么,geth是如何将这些数据存储在key-value数据库levelDB本地的呢? 在这里,我为大家整理一张key-value信息表。

钥匙

价值

阐明

"b".blockNumber.blockHash

blockBody:叔叔+交易

通过区块哈希和高度存储对应的区块叔块和交易信息

“H”.blockHash

块号

通过区块哈希记录区块的区块高度

"h".blockNumber.blockHash

块头

通过区块哈希和高度存储区块的区块头

“r”.blockNumber

收据

通过区块高度记录区块的交易回执记录

“h”.blockNumber

块散列

区块高度对应的区块哈希

"l".txHash

块号

记录交易哈希所在的区块高度

“最后一块”

块散列

更新最后一个区块哈希值

“最后一个标题”

块散列

更新最后一个区块头的位置

注意上面的值信息需要序列化成字节后才能存入leveldb。 序列化是以太坊定制的RLP编码技术。 你有没有想过为什么要加前缀? 比如“b”、“H”等等,第一个好处是可以对不同的数据进行分类,另一个重要的原因是leveldb中数据是按key-value排序存储的,这样在遍历区块头和查询的时候同类型读取性能会更好。

正是因为我们在本地有一些块数据的映射关系,我们可以通过本地数据库提供的少量信息,结合一个或多个键值关系,快速查询到目标数据。 下面我列出了一些常见的以太坊 API。 您如何看待从数据库中查找数据?

通过交易哈希获取交易信息:eth_getTransactionByHash("0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238")查询最后一个区块信息: eth_getBlockByNumber("latest")通过交易哈希获取交易回执eth_getTransactionReceipt("0x444172bef57ad978655171a8af2cfd89baa02a97fcb773067aef7794d6913374")

比特币挖矿怎样算收益_比特币挖矿算力_rx570挖矿比特币算力