github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/power/power_state.go (about) 1 package power 2 3 import ( 4 "fmt" 5 "reflect" 6 7 addr "github.com/filecoin-project/go-address" 8 "github.com/filecoin-project/go-state-types/abi" 9 "github.com/filecoin-project/go-state-types/big" 10 "github.com/filecoin-project/go-state-types/exitcode" 11 cid "github.com/ipfs/go-cid" 12 errors "github.com/pkg/errors" 13 "golang.org/x/xerrors" 14 15 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 16 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 17 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" 18 ) 19 20 // genesis power in bytes = 750,000 GiB 21 var InitialQAPowerEstimatePosition = big.Mul(big.NewInt(750_000), big.NewInt(1<<30)) 22 23 // max chain throughput in bytes per epoch = 120 ProveCommits / epoch = 3,840 GiB 24 var InitialQAPowerEstimateVelocity = big.Mul(big.NewInt(3_840), big.NewInt(1<<30)) 25 26 // Bitwidth of CronEventQueue HAMT determined empirically from mutation 27 // patterns and projections of mainnet data. 28 const CronQueueHamtBitwidth = 6 29 30 // Bitwidth of CronEventQueue AMT determined empirically from mutation 31 // patterns and projections of mainnet data. 32 const CronQueueAmtBitwidth = 6 33 34 // Bitwidth of ProofValidationBatch AMT determined empirically from mutation 35 // pattersn and projections of mainnet data. 36 const ProofValidationBatchAmtBitwidth = 4 37 38 type State struct { 39 TotalRawBytePower abi.StoragePower 40 // TotalBytesCommitted includes claims from miners below min power threshold 41 TotalBytesCommitted abi.StoragePower 42 TotalQualityAdjPower abi.StoragePower 43 // TotalQABytesCommitted includes claims from miners below min power threshold 44 TotalQABytesCommitted abi.StoragePower 45 TotalPledgeCollateral abi.TokenAmount 46 47 // These fields are set once per epoch in the previous cron tick and used 48 // for consistent values across a single epoch's state transition. 49 ThisEpochRawBytePower abi.StoragePower 50 ThisEpochQualityAdjPower abi.StoragePower 51 ThisEpochPledgeCollateral abi.TokenAmount 52 ThisEpochQAPowerSmoothed smoothing.FilterEstimate 53 54 MinerCount int64 55 // Number of miners having proven the minimum consensus power. 56 MinerAboveMinPowerCount int64 57 58 // A queue of events to be triggered by cron, indexed by epoch. 59 CronEventQueue cid.Cid // Multimap, (HAMT[ChainEpoch]AMT[CronEvent]) 60 61 // First epoch in which a cron task may be stored. 62 // Cron will iterate every epoch between this and the current epoch inclusively to find tasks to execute. 63 FirstCronEpoch abi.ChainEpoch 64 65 // Claimed power for each miner. 66 Claims cid.Cid // Map, HAMT[address]Claim 67 68 ProofValidationBatch *cid.Cid // Multimap, (HAMT[Address]AMT[SealVerifyInfo]) 69 } 70 71 type Claim struct { 72 // Miner's proof type used to determine minimum miner size 73 WindowPoStProofType abi.RegisteredPoStProof 74 75 // Sum of raw byte power for a miner's sectors. 76 RawBytePower abi.StoragePower 77 78 // Sum of quality adjusted power for a miner's sectors. 79 QualityAdjPower abi.StoragePower 80 } 81 82 type CronEvent struct { 83 MinerAddr addr.Address 84 CallbackPayload []byte 85 } 86 87 func ConstructState(store adt.Store) (*State, error) { 88 emptyClaimsMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) 89 if err != nil { 90 return nil, xerrors.Errorf("failed to create empty map: %w", err) 91 } 92 emptyCronQueueMMapCid, err := adt.StoreEmptyMultimap(store, CronQueueHamtBitwidth, CronQueueAmtBitwidth) 93 if err != nil { 94 return nil, xerrors.Errorf("failed to create empty multimap: %w", err) 95 } 96 97 return &State{ 98 TotalRawBytePower: abi.NewStoragePower(0), 99 TotalBytesCommitted: abi.NewStoragePower(0), 100 TotalQualityAdjPower: abi.NewStoragePower(0), 101 TotalQABytesCommitted: abi.NewStoragePower(0), 102 TotalPledgeCollateral: abi.NewTokenAmount(0), 103 ThisEpochRawBytePower: abi.NewStoragePower(0), 104 ThisEpochQualityAdjPower: abi.NewStoragePower(0), 105 ThisEpochPledgeCollateral: abi.NewTokenAmount(0), 106 ThisEpochQAPowerSmoothed: smoothing.NewEstimate(InitialQAPowerEstimatePosition, InitialQAPowerEstimateVelocity), 107 FirstCronEpoch: 0, 108 CronEventQueue: emptyCronQueueMMapCid, 109 Claims: emptyClaimsMapCid, 110 MinerCount: 0, 111 MinerAboveMinPowerCount: 0, 112 }, nil 113 } 114 115 // MinerNominalPowerMeetsConsensusMinimum is used to validate Election PoSt 116 // winners outside the chain state. If the miner has over a threshold of power 117 // the miner meets the minimum. If the network is a below a threshold of 118 // miners and has power > zero the miner meets the minimum. 119 func (st *State) MinerNominalPowerMeetsConsensusMinimum(s adt.Store, miner addr.Address) (bool, error) { //nolint:deadcode,unused 120 claims, err := adt.AsMap(s, st.Claims, builtin.DefaultHamtBitwidth) 121 if err != nil { 122 return false, xerrors.Errorf("failed to load claims: %w", err) 123 } 124 125 claim, ok, err := getClaim(claims, miner) 126 if err != nil { 127 return false, err 128 } 129 if !ok { 130 return false, errors.Errorf("no claim for actor %v", miner) 131 } 132 133 minerNominalPower := claim.RawBytePower 134 minerMinPower, err := builtin.ConsensusMinerMinPower(claim.WindowPoStProofType) 135 if err != nil { 136 return false, errors.Wrap(err, "could not get miner min power from proof type") 137 } 138 139 // if miner is larger than min power requirement, we're set 140 if minerNominalPower.GreaterThanEqual(minerMinPower) { 141 return true, nil 142 } 143 144 // otherwise, if ConsensusMinerMinMiners miners meet min power requirement, return false 145 if st.MinerAboveMinPowerCount >= ConsensusMinerMinMiners { 146 return false, nil 147 } 148 149 // If fewer than ConsensusMinerMinMiners over threshold miner can win a block with non-zero power 150 return minerNominalPower.GreaterThan(abi.NewStoragePower(0)), nil 151 } 152 153 // Parameters may be negative to subtract. 154 func (st *State) AddToClaim(s adt.Store, miner addr.Address, power abi.StoragePower, qapower abi.StoragePower) error { 155 claims, err := adt.AsMap(s, st.Claims, builtin.DefaultHamtBitwidth) 156 if err != nil { 157 return xerrors.Errorf("failed to load claims: %w", err) 158 } 159 160 if err := st.addToClaim(claims, miner, power, qapower); err != nil { 161 return xerrors.Errorf("failed to add claim: %w", err) 162 } 163 164 st.Claims, err = claims.Root() 165 if err != nil { 166 return xerrors.Errorf("failed to flush claims: %w", err) 167 } 168 169 return nil 170 } 171 172 func (st *State) GetClaim(s adt.Store, a addr.Address) (*Claim, bool, error) { 173 claims, err := adt.AsMap(s, st.Claims, builtin.DefaultHamtBitwidth) 174 if err != nil { 175 return nil, false, xerrors.Errorf("failed to load claims: %w", err) 176 } 177 return getClaim(claims, a) 178 } 179 180 func (st *State) addToClaim(claims *adt.Map, miner addr.Address, power abi.StoragePower, qapower abi.StoragePower) error { 181 oldClaim, ok, err := getClaim(claims, miner) 182 if err != nil { 183 return fmt.Errorf("failed to get claim: %w", err) 184 } 185 if !ok { 186 return exitcode.ErrNotFound.Wrapf("no claim for actor %v", miner) 187 } 188 189 // TotalBytes always update directly 190 st.TotalQABytesCommitted = big.Add(st.TotalQABytesCommitted, qapower) 191 st.TotalBytesCommitted = big.Add(st.TotalBytesCommitted, power) 192 193 newClaim := Claim{ 194 WindowPoStProofType: oldClaim.WindowPoStProofType, 195 RawBytePower: big.Add(oldClaim.RawBytePower, power), 196 QualityAdjPower: big.Add(oldClaim.QualityAdjPower, qapower), 197 } 198 199 minPower, err := builtin.ConsensusMinerMinPower(oldClaim.WindowPoStProofType) 200 if err != nil { 201 return fmt.Errorf("could not get consensus miner min power: %w", err) 202 } 203 204 prevBelow := oldClaim.RawBytePower.LessThan(minPower) 205 stillBelow := newClaim.RawBytePower.LessThan(minPower) 206 207 if prevBelow && !stillBelow { 208 // just passed min miner size 209 st.MinerAboveMinPowerCount++ 210 st.TotalQualityAdjPower = big.Add(st.TotalQualityAdjPower, newClaim.QualityAdjPower) 211 st.TotalRawBytePower = big.Add(st.TotalRawBytePower, newClaim.RawBytePower) 212 } else if !prevBelow && stillBelow { 213 // just went below min miner size 214 st.MinerAboveMinPowerCount-- 215 st.TotalQualityAdjPower = big.Sub(st.TotalQualityAdjPower, oldClaim.QualityAdjPower) 216 st.TotalRawBytePower = big.Sub(st.TotalRawBytePower, oldClaim.RawBytePower) 217 } else if !prevBelow && !stillBelow { 218 // Was above the threshold, still above 219 st.TotalQualityAdjPower = big.Add(st.TotalQualityAdjPower, qapower) 220 st.TotalRawBytePower = big.Add(st.TotalRawBytePower, power) 221 } 222 223 if newClaim.RawBytePower.LessThan(big.Zero()) { 224 return xerrors.Errorf("negative claimed raw byte power: %v", newClaim.RawBytePower) 225 } 226 if newClaim.QualityAdjPower.LessThan(big.Zero()) { 227 return xerrors.Errorf("negative claimed quality adjusted power: %v", newClaim.QualityAdjPower) 228 } 229 if st.MinerAboveMinPowerCount < 0 { 230 return xerrors.Errorf("negative number of miners larger than min: %v", st.MinerAboveMinPowerCount) 231 } 232 return setClaim(claims, miner, &newClaim) 233 } 234 235 func (st *State) updateStatsForNewMiner(windowPoStProof abi.RegisteredPoStProof) error { 236 minPower, err := builtin.ConsensusMinerMinPower(windowPoStProof) 237 if err != nil { 238 return fmt.Errorf("could not get consensus miner min power: %w", err) 239 } 240 241 if minPower.LessThanEqual(big.Zero()) { 242 st.MinerAboveMinPowerCount++ 243 } 244 return nil 245 } 246 247 func (st *State) deleteClaim(claims *adt.Map, miner addr.Address) (bool, error) { 248 // Note: this flow loads the claim multiple times, unnecessarily. 249 // We should refactor to use claims.Pop(). 250 oldClaim, ok, err := getClaim(claims, miner) 251 if err != nil { 252 return false, fmt.Errorf("failed to get claim: %w", err) 253 } 254 if !ok { 255 return false, nil // no record, we're done 256 } 257 258 // subtract from stats as if we were simply removing power 259 err = st.addToClaim(claims, miner, oldClaim.RawBytePower.Neg(), oldClaim.QualityAdjPower.Neg()) 260 if err != nil { 261 return false, fmt.Errorf("failed to subtract miner power before deleting claim: %w", err) 262 } 263 264 // delete claim from state to invalidate miner 265 return true, claims.Delete(abi.AddrKey(miner)) 266 } 267 268 func getClaim(claims *adt.Map, a addr.Address) (*Claim, bool, error) { 269 var out Claim 270 found, err := claims.Get(abi.AddrKey(a), &out) 271 if err != nil { 272 return nil, false, errors.Wrapf(err, "failed to get claim for address %v", a) 273 } 274 if !found { 275 return nil, false, nil 276 } 277 return &out, true, nil 278 } 279 280 func (st *State) addPledgeTotal(amount abi.TokenAmount) { 281 st.TotalPledgeCollateral = big.Add(st.TotalPledgeCollateral, amount) 282 } 283 284 func (st *State) appendCronEvent(events *adt.Multimap, epoch abi.ChainEpoch, event *CronEvent) error { 285 // if event is in past, alter FirstCronEpoch so it will be found. 286 if epoch < st.FirstCronEpoch { 287 st.FirstCronEpoch = epoch 288 } 289 290 if err := events.Add(epochKey(epoch), event); err != nil { 291 return xerrors.Errorf("failed to store cron event at epoch %v for miner %v: %w", epoch, event, err) 292 } 293 294 return nil 295 } 296 297 func (st *State) updateSmoothedEstimate(delta abi.ChainEpoch) { 298 filterQAPower := smoothing.LoadFilter(st.ThisEpochQAPowerSmoothed, smoothing.DefaultAlpha, smoothing.DefaultBeta) 299 st.ThisEpochQAPowerSmoothed = filterQAPower.NextEstimate(st.ThisEpochQualityAdjPower, delta) 300 } 301 302 func loadCronEvents(mmap *adt.Multimap, epoch abi.ChainEpoch) ([]CronEvent, error) { 303 var events []CronEvent 304 var ev CronEvent 305 err := mmap.ForEach(epochKey(epoch), &ev, func(i int64) error { 306 events = append(events, ev) 307 return nil 308 }) 309 return events, err 310 } 311 312 func setClaim(claims *adt.Map, a addr.Address, claim *Claim) error { 313 if claim.RawBytePower.LessThan(big.Zero()) { 314 return xerrors.Errorf("negative claim raw power %v", claim.RawBytePower) 315 } 316 if claim.QualityAdjPower.LessThan(big.Zero()) { 317 return xerrors.Errorf("negative claim quality-adjusted power %v", claim.QualityAdjPower) 318 } 319 if err := claims.Put(abi.AddrKey(a), claim); err != nil { 320 return xerrors.Errorf("failed to put claim with address %s power %v: %w", a, claim, err) 321 } 322 return nil 323 } 324 325 // CurrentTotalPower returns current power values accounting for minimum miner 326 // and minimum power 327 func CurrentTotalPower(st *State) (abi.StoragePower, abi.StoragePower) { 328 if st.MinerAboveMinPowerCount < ConsensusMinerMinMiners { 329 return st.TotalBytesCommitted, st.TotalQABytesCommitted 330 } 331 return st.TotalRawBytePower, st.TotalQualityAdjPower 332 } 333 334 func epochKey(e abi.ChainEpoch) abi.Keyer { 335 return abi.IntKey(int64(e)) 336 } 337 338 func init() { 339 // Check that ChainEpoch is indeed a signed integer to confirm that epochKey is making the right interpretation. 340 var e abi.ChainEpoch 341 if reflect.TypeOf(e).Kind() != reflect.Int64 { 342 panic("incorrect chain epoch encoding") 343 } 344 345 }