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, 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,
})
}