初链主网Beta版慢链挖矿解析

初链主网Beta版于新加坡时间2018年09月28日08:00正式上线,在此之前,07:56分PBFT委员会第一次共识出块和TrueChain fPOW创世区块被挖出。下面我们就来谈谈慢链的出矿。

1 启动节点自动挖矿

本帖以docker挖矿为例,详细操作见docker版挖矿教程 。节点启动分为参不参加委员会竞选挖矿。

1.1 参与委员会竞选节点挖矿

1)挖矿只挖水果

docker run -v $PWD:/truechain-engineering-code -it -p 30311:30311 -p 30310:30310 -p 30303:30303 -p 9215:9215 getrue --datadir /truechain-engineering-code/data --config /truechain-engineering-code/config --testnet --mine  --minefruit --election console

2)挖矿水果和区块

docker run -v $PWD:/truechain-engineering-code -it -p 30311:30311 -p 30310:30310 -p 30303:30303 -p 9215:9215 getrue --datadir /truechain-engineering-code/data --config /truechain-engineering-code/config --testnet --mine --election console

1.2 非参与委员会竞选节点挖矿

1)挖矿只挖水果

docker run -v $PWD:/truechain-engineering-code -it -p 30311:30311 -p 30310:30310 -p 30303:30303 -p 9215:9215 getrue --datadir /truechain-engineering-code/data --config /truechain-engineering-code/config --testnet --mine --minefruit console

2)挖矿水果和区块

docker run -v $PWD:/truechain-engineering-code -it -p 30311:30311 -p 30310:30310 -p 30303:30303 -p 9215:9215 getrue --datadir /truechain-engineering-code/data --config /truechain-engineering-code/config --testnet --mine console

1.3 启动命令解析

  • -p 将容器中的端口映射到宿主机
  • –datadir 数据存储的文件夹
  • –minefruit 指定挖矿只挖水果
  • –election 本节点参与委员会竞选

2 FPOW机制挖矿

2.1 挖矿流程

初链采用了水果链(FruitChain)的设计来保证在挖矿过程中所有参与者的公平性。这是一种全新的设计挖矿的设计理念,称之为FPOW。这种设计有效的解决了算力差距导致的小算力矿工挖不到矿的不足,保证了所有矿工的公平性,因为在挖矿过程中挖到相对简单的水果(fruit)也是有奖励的。挖到的水果称之为水果链(FruitChain)。其大致的流程如下图所示。
image

2.2 代码实现

上图流程中的代码实现我们可以从源码的truechain-engineering-code/miner/worker.go中找到。

block := result.Block
//work := result.Work

if block.IsFruit() {
	if block.FastNumber() == nil {
		// if it does't include a fast block signs, it's not a fruit
		continue
	}
	if block.FastNumber().Cmp(common.Big0) == 0 {
		continue
	}

	//log.Info("🍒 —-------mined fruit"," FB NUMBER",block.FastNumber())

	// add fruit once
	if self.FastBlockNumber != nil{
		if self.FastBlockNumber.Cmp(block.FastNumber()) !=0 {
			log.Info("🍒  ----mined fruit 1","number",block.FastNumber(), "diff", block.FruitDifficulty(), "hash", block.Hash(), "signs", len(block.Signs()))
			//log.Info("not same fruits")
			var newFruits []*types.SnailBlock
			newFruits = append(newFruits, block)
			self.etrue.SnailPool().AddRemoteFruits(newFruits)
		}
	}else{
		log.Info("🍒 ----mined fruit 2","number",block.FastNumber(), "diff", block.FruitDifficulty(), "hash", block.Hash(), "signs", len(block.Signs()))
		var newFruits []*types.SnailBlock
		newFruits = append(newFruits, block)
		self.etrue.SnailPool().AddRemoteFruits(newFruits)
	}

	// make sure the fast number has been fruit
	self.FastBlockNumber.SetUint64(block.FastNumber().Uint64())

	// only have fast block not fruits we need commit new work
	if self.current.fruits == nil{
		self.atCommintNewWoker = false
		// post msg for commitnew work
		var (
			events []interface{}
		)
		events = append(events, chain.NewMinedEvent{Block: block})
		self.chain.PostChainEvents(events)
	}
} else {
	if block.Fruits() == nil{
		self.atCommintNewWoker = false
		continue
	}

	fruits := block.Fruits()
	log.Info("+++++ mined block  ---  ","block number",block.Number(), "fruits", len(fruits), "first", fruits[0].FastNumber(), "end", fruits[len(fruits) - 1].FastNumber())

	stat, err := self.chain.WriteCanonicalBlock(block)
	if err != nil {
		log.Error("Failed writing block to chain", "err", err)
		continue
	}

	// Broadcast the block and announce chain insertion event
	self.mux.Post(chain.NewMinedBlockEvent{Block: block})
	var (
		events []interface{}
	)

	events = append(events, chain.NewMinedEvent{Block: block})

	if stat == chain.CanonStatTy {
		events = append(events, chain.ChainEvent{Block: block, Hash: block.Hash()})
		events = append(events, chain.ChainHeadEvent{Block: block})
	}
	self.chain.PostChainEvents(events)

	// Insert the block into the set of pending ones to wait for confirmations
	self.unconfirmed.Insert(block.NumberU64(), block.Hash())

	self.atCommintNewWoker = false
}
  1. block := result.Block:首先获取到结果中的block(姑且先叫他block,应为他还有可能只是一个水果而不是块)。
  2. 通过block.IsFruit()方法判断此block是否是水果(fruit),如果返回true的话,进入方法体,执行水果的操作,反之,执行块的操作。
  3. block.IsFruit()返回true。通过self.etrue.SnailPool().AddRemoteFruits(newFruits)将该block添加到水果池(snailpool)中。
  4. block.IsFruit()返回false,并且该block中含有fruit,那么提交委员会确认就可以执行上链操作了,self.chain.WriteCanonicalBlock(block)。

2.3 水果新鲜度确认

初链通过fPOW机制可以很有效的避免自私挖矿,其原理就是给水果赋予新鲜度的概念。如果一个自私的矿工在挖到块之后不提交委员会确认,自己想再挖到更长的链之后再广播的话,那么,对不起,之前挖到的水果很可能就已经过期了,那么之前没广播的链也就作废了。所以矿工会避免水果过期而及时把自己挖到的块提交委员会确认并上链,从而有效的避免自私挖矿。下面是水果新鲜度确认的代码实现。截取自truechain-engineering-code/consensus/minerva/consensus.go。

func (m *Minerva) VerifyFreshness(fruit, block *types.SnailHeader) error {
	var headerNumber *big.Int
	if block == nil {
		// when block is nil, is used to verify new fruits for next block
		headerNumber = new(big.Int).Add(m.sbc.CurrentHeader().Number, common.Big1)
	} else {
		headerNumber = block.Number
	}
	// check freshness
	pointer := m.sbc.GetHeaderByNumber(fruit.PointerNumber.Uint64())
	if pointer == nil {
		return types.ErrSnailHeightNotYet
	}
	if pointer.Hash() != fruit.PointerHash {
		log.Debug("VerifyFreshness get pointer failed.", "fruit", fruit.FastNumber, "pointerNumber", fruit.PointerNumber, "pointerHash", fruit.PointerHash,
			"fruitNumber", fruit.Number, "pointer", pointer.Hash())
		return consensus.ErrUnknownPointer
	}
	freshNumber := new(big.Int).Sub(headerNumber, pointer.Number)
	if freshNumber.Cmp(params.FruitFreshness) > 0 {
		log.Debug("VerifyFreshness failed.", "fruit sb", fruit.Number, "fruit fb", fruit.FastNumber, "poiner", pointer.Number, "current", headerNumber)
		return consensus.ErrFreshness
	}

	return nil
}

从上面的代码块中可知,水果的hash和指针的hash值不相等以及水果新鲜度值大于设置的新鲜值的时候,均会返回VerifyFreshness failed,即该水果已经过期了。

2.4 挖矿奖励分配

我们说fPOW机制对所有的矿工都是公平的,不会因为普通矿工的算力不够而挖不到块导致得不到奖励,因为就算你算力不够,挖到水果的概率还是比较高的。那么,这个奖励是怎么分配的呢?我们来看分配的代码。

// AccumulateRewardsFast credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewardsFast(election consensus.CommitteeElection, state *state.StateDB, header *types.Header, sBlock *types.SnailBlock) error {
	committeeCoin, minerCoin, minerFruitCoin, e := getBlockReward(header.Number)

	if e != nil {
		return e
	}

	//miner's award
	state.AddBalance(sBlock.Coinbase(), minerCoin)
	LogPrint("miner's award", sBlock.Coinbase(), minerCoin)

	//miner fruit award
	blockFruits := sBlock.Body().Fruits
	blockFruitsLen := big.NewInt(int64(len(blockFruits)))
	if len(blockFruits) > 0 {
		minerFruitCoinOne := new(big.Int).Div(minerFruitCoin, blockFruitsLen)
		for _, v := range sBlock.Body().Fruits {
			state.AddBalance(v.Coinbase(), minerFruitCoinOne)
			LogPrint("minerFruit", v.Coinbase(), minerFruitCoinOne)
		}
	} else {
		return consensus.ErrInvalidBlock
	}

	//committee's award
	committeeCoinFruit := new(big.Int).Div(committeeCoin, blockFruitsLen)

	//all fail committee coinBase
	failAddr := make(map[common.Address]bool)

	for _, fruit := range blockFruits {
		signs := fruit.Body().Signs

		committeeMembers, errs := election.VerifySigns(signs)

		if len(committeeMembers) != len(errs) {
			return consensus.ErrInvalidSignsLength
		}

		//Effective and not evil
		var fruitOkAddr []common.Address
		for i, cm := range committeeMembers {
			if errs[i] != nil {
				continue
			}
			cmPubAddr := crypto.PubkeyToAddress(*cm.Publickey)

			if signs[i].Result == types.VoteAgree {
				if _, ok := failAddr[cmPubAddr]; !ok {
					fruitOkAddr = append(fruitOkAddr, cm.Coinbase)
				}
			} else {
				failAddr[cmPubAddr] = false
			}
		}

		if len(fruitOkAddr) == 0 {
			log.Error("fruitOkAddr", "Error", consensus.ErrInvalidSignsLength.Error())
			return nil
		}

		// Equal by fruit
		committeeCoinFruitMember := new(big.Int).Div(committeeCoinFruit, big.NewInt(int64(len(fruitOkAddr))))
		for _, v := range fruitOkAddr {
			state.AddBalance(v, committeeCoinFruitMember)
			LogPrint("committee", v, committeeCoinFruitMember)
		}
	}

	return nil
}

从上面这段代码中,我们可以发现挖矿奖励是分配给三方的,分别是:

  • 区块矿工
  • 水果矿工
  • 委员会

也许有人会问啦,如果区块不包含水果,是不是就只要分给区块矿工和委员会两方就可以啦,答案是否定的,如果这个区块不包含水果(len(sBlock.Body().Fruits)<=0),那么这个块是不合法的(ErrInvalidBlock)。
分配流程为:

  1. 计算区块的奖励,getBlockReward()
  2. 计算区块矿工,水果矿工,委员会分别应得的奖励
  3. 给区块矿工,水果矿工,签名的委员会成员分发奖励。

3 总结

初链使用了fPOW机制,这种设计有效的解决了算力差距导致的小算力矿工挖不到矿的不足,保证了所有矿工的公平性。这种机制的出矿流程是矿工挖到矿后判断是块还是水果,矿工挖到水果后,会将水果放到一个水果池(fruit pool)中,待到挖到块(block)后,便从水果池中取出水果放置在区块中,区块进入委员会进行确认,达成共识就上链。上链后区块矿工,水果矿工及签名的委员会成员都能分到该区块的奖励。

阅读更多

更多精彩内容