初链主网Beta版于新加坡时间2018年09月28日08:00正式上线,在此之前,07:56分PBFT委员会第一次共识出块和TrueChain fPOW创世区块被挖出。下面我们就来谈谈慢链的出矿。
本帖以docker挖矿为例,详细操作见docker版挖矿教程 。节点启动分为参不参加委员会竞选挖矿。
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)挖矿只挖水果
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
初链采用了水果链(FruitChain)的设计来保证在挖矿过程中所有参与者的公平性。这是一种全新的设计挖矿的设计理念,称之为FPOW。这种设计有效的解决了算力差距导致的小算力矿工挖不到矿的不足,保证了所有矿工的公平性,因为在挖矿过程中挖到相对简单的水果(fruit)也是有奖励的。挖到的水果称之为水果链(FruitChain)。其大致的流程如下图所示。
上图流程中的代码实现我们可以从源码的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
}
初链通过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,即该水果已经过期了。
我们说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)。
分配流程为:
初链使用了fPOW机制,这种设计有效的解决了算力差距导致的小算力矿工挖不到矿的不足,保证了所有矿工的公平性。这种机制的出矿流程是矿工挖到矿后判断是块还是水果,矿工挖到水果后,会将水果放到一个水果池(fruit pool)中,待到挖到块(block)后,便从水果池中取出水果放置在区块中,区块进入委员会进行确认,达成共识就上链。上链后区块矿工,水果矿工及签名的委员会成员都能分到该区块的奖励。