2021-07-05-MineOne过程 Block Chain Lotus MineOne 过程 step1 :compute ticket func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry, base *MiningBase, mbi *api.MiningBaseInfo) (*types.Ticket, error) { // 序列化 miner id buf := new(bytes.Buffer) if err := m.address.MarshalCBOR(buf); err != nil { return nil, xerrors.Errorf("failed to marshal address to cbor: %w", err) } // round 为实际要计算的高度 round := base.TipSet.Height() + base.NullRounds + 1 if round > build.UpgradeSmokeHeight { buf.Write(base.TipSet.MinTicket().VRFProof) } // 根据 beccon miner round 随机生成 input 数据 // 采用 black2b hash 算法 input, err := store.DrawRandomness(brand.Data, crypto.DomainSeparationTag_TicketProduction, round-build.TicketRandomnessLookback, buf.Bytes()) if err != nil { return nil, err } // 计算 vrf 数据 // bls wallet 对 input 签名的结果 vrfOut, err := gen.ComputeVRF(ctx, m.api.WalletSign, mbi.WorkerKey, input) if err != nil { return nil, err } return &types.Ticket{ VRFProof: vrfOut, }, nil } step2:判断在回合中是否胜出 func IsRoundWinner(ctx context.Context, ts *types.TipSet, round abi.ChainEpoch, miner address.Address, brand types.BeaconEntry, mbi *api.MiningBaseInfo, a MiningCheckAPI) (*types.ElectionProof, error) { buf := new(bytes.Buffer) if err := miner.MarshalCBOR(buf); err != nil { return nil, xerrors.Errorf("failed to cbor marshal address: %w", err) } electionRand, err := store.DrawRandomness(brand.Data, crypto.DomainSeparationTag_ElectionProofProduction, round, buf.Bytes()) if err != nil { return nil, xerrors.Errorf("failed to draw randomness: %w", err) } log.Infof("IsRoundWinner DrawRandomness: brand= %x, stage= ElectionProofProduction, round= %v, beacon= %x, electionRand= %x, WorkerKey= %s", brand.Data, round, buf.Bytes(), electionRand, mbi.WorkerKey.String()) vrfout, err := ComputeVRF(ctx, a.WalletSign, mbi.WorkerKey, electionRand) if err != nil { return nil, xerrors.Errorf("failed to compute VRF: %w", err) } // 进入ComputeWinCount ep := &types.ElectionProof{VRFProof: vrfout} j := ep.ComputeWinCount(mbi.MinerPower, mbi.NetworkPower) ep.WinCount = j log.Infof("IsRoundWinner ComputeWinCount: vrfout= %x, mPower= %x, nPower= %x, winCount= %v", vrfout, mbi.MinerPower, mbi.NetworkPower, ep.WinCount) if j < 1 { return nil, nil } return ep, nil } // 计算 wincount // ComputeWinCount uses VRFProof to compute number of wins // The algorithm is based on Algorand's Sortition with Binomial distribution // replaced by Poisson distribution. func (ep *ElectionProof) ComputeWinCount(power BigInt, totalPower BigInt) int64 { h := blake2b.Sum256(ep.VRFProof) lhs := BigFromBytes(h[:]).Int // 256bits, assume Q.256 so [0, 1) // We are calculating upside-down CDF of Poisson distribution with // rate λ=power*E/totalPower // Steps: // 1. calculate λ=power*E/totalPower // 2. calculate elam = exp(-λ) // 3. Check how many times we win: // j = 0 // pmf = elam // rhs = 1 - pmf // for h(vrf) < rhs: j++; pmf = pmf * lam / j; rhs = rhs - pmf // 求得泊松分布的 lambda // lam = (power * 5) << 256) / total lam := lambda(power.Int, totalPower.Int) // Q.256 // 根据 miner 算力和全网算力求得 k=0 时的泊松分布 p, rhs := newPoiss(lam) var j int64 // Max Wincount = 15 for lhs.Cmp(rhs) < 0 && j < MaxWinCount { rhs = p.next() j++ } return j } step3:compute proof 首先调用 store.DrawRandomness 产生需要进行计算证明的数据,和 step 中调用该方法时传入的参数一致 然后调用 ComputeProof() 进行计算,将 store.DrawRandomness 产生的源数据和 sector 信息传入,进行一些列处理后,调用 rust 计算 func (wpp *StorageWpp) ComputeProof(ctx context.Context, ssi []builtin.SectorInfo, rand abi.PoStRandomness) ([]builtin.PoStProof, error) { if build.InsecurePoStValidation { return []builtin.PoStProof{{ProofBytes: []byte("valid proof")}}, nil } log.Infof("Computing WinningPoSt ;%+v; %v", ssi, rand) start := build.Clock.Now() proof, err := wpp.prover.GenerateWinningPoSt(ctx, wpp.miner, ssi, rand) if err != nil { return nil, err } log.Infof("GenerateWinningPoSt took %s", time.Since(start)) return proof, nil } step4:mpool select func (a *MpoolAPI) MpoolSelect(ctx context.Context, tsk types.TipSetKey, ticketQuality float64) ([]*types.SignedMessage, error) { ts, err := a.Chain.GetTipSetFromKey(tsk) if err != nil { return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) } return a.Mpool.SelectMessages(ctx, ts, ticketQuality) } func (mp *MessagePool) SelectMessages(ctx context.Context, ts *types.TipSet, tq float64) (msgs []*types.SignedMessage, err error) { mp.curTsLk.Lock() defer mp.curTsLk.Unlock() mp.lk.Lock() defer mp.lk.Unlock() // if the ticket quality is high enough that the first block has higher probability // than any other block, then we don't bother with optimal selection because the // first block will always have higher effective performance // 两种 select messages 的方式 // 1. 贪婪:获取 mpool pending 中的全部消息,将 message 组装成 message chain,只受 block 的最大 gas limit 限制 // 2. 择优:获取 mpool pending 中的全部消息,将 message 组装成 message chain,受 message chain 的最大数量(15个)、最大gas limit、评优计算限制 if tq > 0.84 { msgs, err = mp.selectMessagesGreedy(ctx, mp.curTs, ts) } else { msgs, err = mp.selectMessagesOptimal(ctx, mp.curTs, ts, tq) } if err != nil { return nil, err } // MaxBlockMessages = 16000 if len(msgs) > MaxBlockMessages { msgs = msgs[:MaxBlockMessages] } return msgs, nil } step5:create block 把 ticket 数据、IsRoundWinner 产生的数据、beacon、ComputeProof 产生的数据、msgs 进行打包 func (m *Miner) createBlock(base *MiningBase, addr address.Address, ticket *types.Ticket, eproof *types.ElectionProof, bvals []types.BeaconEntry, wpostProof []proof2.PoStProof, msgs []*types.SignedMessage) (*types.BlockMsg, error) { uts := base.TipSet.MinTimestamp() + build.BlockDelaySecs*(uint64(base.NullRounds)+1) nheight := base.TipSet.Height() + base.NullRounds + 1 // why even return this? that api call could just submit it for us return m.api.MinerCreateBlock(context.TODO(), &api.BlockTemplate{ Miner: addr, Parents: base.TipSet.Key(), Ticket: ticket, Eproof: eproof, BeaconValues: bvals, Messages: msgs, Epoch: nheight, Timestamp: uts, WinningPoStProof: wpostProof, }) }