lotus/cmd/lotus-sim/simulation/state.go
2021-06-18 15:44:34 -07:00

191 lines
5.2 KiB
Go

package simulation
import (
"context"
"math/rand"
"sort"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/lotus/chain/actors/builtin/miner"
"github.com/filecoin-project/lotus/chain/types"
)
type perm struct {
miners []address.Address
offset int
}
func (p *perm) shuffle() {
rand.Shuffle(len(p.miners), func(i, j int) {
p.miners[i], p.miners[j] = p.miners[j], p.miners[i]
})
}
func (p *perm) next() address.Address {
next := p.miners[p.offset]
p.offset++
p.offset %= len(p.miners)
return next
}
func (p *perm) add(addr address.Address) {
p.miners = append(p.miners, addr)
}
func (p *perm) len() int {
return len(p.miners)
}
type simulationState struct {
*Simulation
// TODO Ideally we'd "learn" this distribution from the network. But this is good enough for
// now. The tiers represent the top 1%, top 10%, and everyone else. When sealing sectors, we
// seal a group of sectors for the top 1%, a group (half that size) for the top 10%, and one
// sector for everyone else. We really should pick a better algorithm.
minerDist struct {
top1, top10, rest perm
}
// We track the window post periods per miner and assume that no new miners are ever added.
wpostPeriods map[int][]address.Address // (epoch % (epochs in a deadline)) -> miner
// We cache all miner infos for active miners and assume no new miners join.
minerInfos map[address.Address]*miner.MinerInfo
// We record all pending window post messages, and the epoch up through which we've
// generated window post messages.
pendingWposts []*types.Message
nextWpostEpoch abi.ChainEpoch
commitQueue commitQueue
}
func loadSimulationState(ctx context.Context, sim *Simulation) (*simulationState, error) {
state := &simulationState{Simulation: sim}
currentEpoch := sim.head.Height()
// Lookup the current power table and the power table 2 weeks ago (for onboarding rate
// projections).
currentPowerTable, err := sim.loadClaims(ctx, currentEpoch)
if err != nil {
return nil, err
}
var lookbackEpoch abi.ChainEpoch
//if epoch > onboardingProjectionLookback {
// lookbackEpoch = epoch - onboardingProjectionLookback
//}
// TODO: Fixme? I really want this to not suck with snapshots.
lookbackEpoch = 770139 // hard coded for now.
lookbackPowerTable, err := sim.loadClaims(ctx, lookbackEpoch)
if err != nil {
return nil, err
}
// Now load miner state info.
store := sim.Chainstore.ActorStore(ctx)
st, err := sim.stateTree(ctx)
if err != nil {
return nil, err
}
type onboardingInfo struct {
addr address.Address
onboardingRate uint64
}
commitRand, err := sim.postChainCommitInfo(ctx, currentEpoch)
if err != nil {
return nil, err
}
sealList := make([]onboardingInfo, 0, len(currentPowerTable))
state.wpostPeriods = make(map[int][]address.Address, miner.WPoStChallengeWindow)
state.minerInfos = make(map[address.Address]*miner.MinerInfo, len(currentPowerTable))
state.commitQueue.advanceEpoch(state.nextEpoch())
for addr, claim := range currentPowerTable {
// Load the miner state.
minerActor, err := st.GetActor(addr)
if err != nil {
return nil, err
}
minerState, err := miner.Load(store, minerActor)
if err != nil {
return nil, err
}
info, err := minerState.Info()
if err != nil {
return nil, err
}
state.minerInfos[addr] = &info
// Queue up PoSts
err = state.stepWindowPoStsMiner(ctx, addr, minerState, currentEpoch, commitRand)
if err != nil {
return nil, err
}
// Qeueu up any pending prove commits.
err = state.loadProveCommitsMiner(ctx, addr, minerState)
if err != nil {
return nil, err
}
// Record when we need to prove for this miner.
dinfo, err := minerState.DeadlineInfo(state.nextEpoch())
if err != nil {
return nil, err
}
dinfo = dinfo.NextNotElapsed()
ppOffset := int(dinfo.PeriodStart % miner.WPoStChallengeWindow)
state.wpostPeriods[ppOffset] = append(state.wpostPeriods[ppOffset], addr)
sectorsAdded := sectorsFromClaim(info.SectorSize, claim)
if lookbackClaim, ok := lookbackPowerTable[addr]; !ok {
sectorsAdded -= sectorsFromClaim(info.SectorSize, lookbackClaim)
}
// NOTE: power _could_ have been lost, but that's too much of a pain to care
// about. We _could_ look for faulty power by iterating through all
// deadlines, but I'd rather not.
if sectorsAdded > 0 {
sealList = append(sealList, onboardingInfo{addr, uint64(sectorsAdded)})
}
}
// We're already done loading for the _next_ epoch.
// Next time, we need to load for the next, next epoch.
// TODO: fix this insanity.
state.nextWpostEpoch = state.nextEpoch() + 1
// Now that we have a list of sealing miners, sort them into percentiles.
sort.Slice(sealList, func(i, j int) bool {
return sealList[i].onboardingRate < sealList[j].onboardingRate
})
for i, oi := range sealList {
var dist *perm
if i < len(sealList)/100 {
dist = &state.minerDist.top1
} else if i < len(sealList)/10 {
dist = &state.minerDist.top10
} else {
dist = &state.minerDist.rest
}
dist.add(oi.addr)
}
state.minerDist.top1.shuffle()
state.minerDist.top10.shuffle()
state.minerDist.rest.shuffle()
return state, nil
}
func (ss *simulationState) nextEpoch() abi.ChainEpoch {
return ss.GetHead().Height() + 1
}