github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/power/power_actor.go (about) 1 package power 2 3 import ( 4 "bytes" 5 6 addr "github.com/filecoin-project/go-address" 7 "github.com/filecoin-project/go-state-types/abi" 8 "github.com/filecoin-project/go-state-types/big" 9 "github.com/filecoin-project/go-state-types/cbor" 10 "github.com/filecoin-project/go-state-types/exitcode" 11 rtt "github.com/filecoin-project/go-state-types/rt" 12 power0 "github.com/filecoin-project/specs-actors/actors/builtin/power" 13 "github.com/ipfs/go-cid" 14 15 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 16 initact "github.com/filecoin-project/specs-actors/v4/actors/builtin/init" 17 "github.com/filecoin-project/specs-actors/v4/actors/runtime" 18 "github.com/filecoin-project/specs-actors/v4/actors/runtime/proof" 19 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 20 "github.com/filecoin-project/specs-actors/v4/actors/util/smoothing" 21 ) 22 23 type Runtime = runtime.Runtime 24 25 type SectorTermination int64 26 27 const ( 28 ErrTooManyProveCommits = exitcode.FirstActorSpecificExitCode + iota 29 ) 30 31 type Actor struct{} 32 33 func (a Actor) Exports() []interface{} { 34 return []interface{}{ 35 builtin.MethodConstructor: a.Constructor, 36 2: a.CreateMiner, 37 3: a.UpdateClaimedPower, 38 4: a.EnrollCronEvent, 39 5: a.OnEpochTickEnd, 40 6: a.UpdatePledgeTotal, 41 7: nil, // deprecated 42 8: a.SubmitPoRepForBulkVerify, 43 9: a.CurrentTotalPower, 44 } 45 } 46 47 func (a Actor) Code() cid.Cid { 48 return builtin.StoragePowerActorCodeID 49 } 50 51 func (a Actor) IsSingleton() bool { 52 return true 53 } 54 55 func (a Actor) State() cbor.Er { 56 return new(State) 57 } 58 59 var _ runtime.VMActor = Actor{} 60 61 // Storage miner actor constructor params are defined here so the power actor can send them to the init actor 62 // to instantiate miners. 63 // Changed since v2: 64 // - Seal proof type replaced with PoSt proof type 65 type MinerConstructorParams struct { 66 OwnerAddr addr.Address 67 WorkerAddr addr.Address 68 ControlAddrs []addr.Address 69 WindowPoStProofType abi.RegisteredPoStProof 70 PeerId abi.PeerID 71 Multiaddrs []abi.Multiaddrs 72 } 73 74 //////////////////////////////////////////////////////////////////////////////// 75 // Actor methods 76 //////////////////////////////////////////////////////////////////////////////// 77 78 func (a Actor) Constructor(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue { 79 rt.ValidateImmediateCallerIs(builtin.SystemActorAddr) 80 81 st, err := ConstructState(adt.AsStore(rt)) 82 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to construct state") 83 rt.StateCreate(st) 84 return nil 85 } 86 87 // Changed since v2: 88 // - Seal proof type replaced with PoSt proof types 89 type CreateMinerParams struct { 90 Owner addr.Address 91 Worker addr.Address 92 WindowPoStProofType abi.RegisteredPoStProof 93 Peer abi.PeerID 94 Multiaddrs []abi.Multiaddrs 95 } 96 97 //type CreateMinerReturn struct { 98 // IDAddress addr.Address // The canonical ID-based address for the actor. 99 // RobustAddress addr.Address // A more expensive but re-org-safe address for the newly created actor. 100 //} 101 type CreateMinerReturn = power0.CreateMinerReturn 102 103 func (a Actor) CreateMiner(rt Runtime, params *CreateMinerParams) *CreateMinerReturn { 104 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 105 106 ctorParams := MinerConstructorParams{ 107 OwnerAddr: params.Owner, 108 WorkerAddr: params.Worker, 109 WindowPoStProofType: params.WindowPoStProofType, 110 PeerId: params.Peer, 111 Multiaddrs: params.Multiaddrs, 112 } 113 ctorParamBuf := new(bytes.Buffer) 114 err := ctorParams.MarshalCBOR(ctorParamBuf) 115 builtin.RequireNoErr(rt, err, exitcode.ErrSerialization, "failed to serialize miner constructor params %v", ctorParams) 116 117 var addresses initact.ExecReturn 118 code := rt.Send( 119 builtin.InitActorAddr, 120 builtin.MethodsInit.Exec, 121 &initact.ExecParams{ 122 CodeCID: builtin.StorageMinerActorCodeID, 123 ConstructorParams: ctorParamBuf.Bytes(), 124 }, 125 rt.ValueReceived(), // Pass on any value to the new actor. 126 &addresses, 127 ) 128 builtin.RequireSuccess(rt, code, "failed to init new actor") 129 130 var st State 131 rt.StateTransaction(&st, func() { 132 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 133 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 134 135 err = setClaim(claims, addresses.IDAddress, &Claim{params.WindowPoStProofType, abi.NewStoragePower(0), abi.NewStoragePower(0)}) 136 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to put power in claimed table while creating miner") 137 138 st.MinerCount += 1 139 140 // Ensure new claim updates all power stats 141 err = st.updateStatsForNewMiner(params.WindowPoStProofType) 142 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed update power stats for new miner %v", addresses.IDAddress) 143 144 st.Claims, err = claims.Root() 145 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims") 146 }) 147 return &CreateMinerReturn{ 148 IDAddress: addresses.IDAddress, 149 RobustAddress: addresses.RobustAddress, 150 } 151 } 152 153 //type UpdateClaimedPowerParams struct { 154 // RawByteDelta abi.StoragePower 155 // QualityAdjustedDelta abi.StoragePower 156 //} 157 type UpdateClaimedPowerParams = power0.UpdateClaimedPowerParams 158 159 // Adds or removes claimed power for the calling actor. 160 // May only be invoked by a miner actor. 161 func (a Actor) UpdateClaimedPower(rt Runtime, params *UpdateClaimedPowerParams) *abi.EmptyValue { 162 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 163 minerAddr := rt.Caller() 164 var st State 165 rt.StateTransaction(&st, func() { 166 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 167 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 168 169 err = st.addToClaim(claims, minerAddr, params.RawByteDelta, params.QualityAdjustedDelta) 170 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update power raw %s, qa %s", params.RawByteDelta, params.QualityAdjustedDelta) 171 172 st.Claims, err = claims.Root() 173 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims") 174 }) 175 return nil 176 } 177 178 //type EnrollCronEventParams struct { 179 // EventEpoch abi.ChainEpoch 180 // Payload []byte 181 //} 182 type EnrollCronEventParams = power0.EnrollCronEventParams 183 184 func (a Actor) EnrollCronEvent(rt Runtime, params *EnrollCronEventParams) *abi.EmptyValue { 185 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 186 minerAddr := rt.Caller() 187 minerEvent := CronEvent{ 188 MinerAddr: minerAddr, 189 CallbackPayload: params.Payload, 190 } 191 192 // Ensure it is not possible to enter a large negative number which would cause problems in cron processing. 193 if params.EventEpoch < 0 { 194 rt.Abortf(exitcode.ErrIllegalArgument, "cron event epoch %d cannot be less than zero", params.EventEpoch) 195 } 196 197 var st State 198 rt.StateTransaction(&st, func() { 199 events, err := adt.AsMultimap(adt.AsStore(rt), st.CronEventQueue, CronQueueHamtBitwidth, CronQueueAmtBitwidth) 200 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events") 201 202 err = st.appendCronEvent(events, params.EventEpoch, &minerEvent) 203 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to enroll cron event") 204 205 st.CronEventQueue, err = events.Root() 206 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush cron events") 207 }) 208 return nil 209 } 210 211 // Called by Cron. 212 func (a Actor) OnEpochTickEnd(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue { 213 rt.ValidateImmediateCallerIs(builtin.CronActorAddr) 214 215 a.processBatchProofVerifies(rt) 216 a.processDeferredCronEvents(rt) 217 218 var st State 219 rt.StateTransaction(&st, func() { 220 // update next epoch's power and pledge values 221 // this must come before the next epoch's rewards are calculated 222 // so that next epoch reward reflects power added this epoch 223 rawBytePower, qaPower := CurrentTotalPower(&st) 224 st.ThisEpochPledgeCollateral = st.TotalPledgeCollateral 225 st.ThisEpochQualityAdjPower = qaPower 226 st.ThisEpochRawBytePower = rawBytePower 227 // we can now assume delta is one since cron is invoked on every epoch. 228 st.updateSmoothedEstimate(abi.ChainEpoch(1)) 229 }) 230 231 // update network KPI in RewardActor 232 code := rt.Send( 233 builtin.RewardActorAddr, 234 builtin.MethodsReward.UpdateNetworkKPI, 235 &st.ThisEpochRawBytePower, 236 abi.NewTokenAmount(0), 237 &builtin.Discard{}, 238 ) 239 builtin.RequireSuccess(rt, code, "failed to update network KPI with Reward Actor") 240 241 return nil 242 } 243 244 func (a Actor) UpdatePledgeTotal(rt Runtime, pledgeDelta *abi.TokenAmount) *abi.EmptyValue { 245 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 246 var st State 247 rt.StateTransaction(&st, func() { 248 validateMinerHasClaim(rt, st, rt.Caller()) 249 st.addPledgeTotal(*pledgeDelta) 250 builtin.RequireState(rt, st.TotalPledgeCollateral.GreaterThanEqual(big.Zero()), "negative total pledge collateral %v", st.TotalPledgeCollateral) 251 }) 252 return nil 253 } 254 255 // GasOnSubmitVerifySeal is amount of gas charged for SubmitPoRepForBulkVerify 256 // This number is empirically determined 257 const GasOnSubmitVerifySeal = 34721049 258 259 func (a Actor) SubmitPoRepForBulkVerify(rt Runtime, sealInfo *proof.SealVerifyInfo) *abi.EmptyValue { 260 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 261 262 minerAddr := rt.Caller() 263 264 var st State 265 rt.StateTransaction(&st, func() { 266 validateMinerHasClaim(rt, st, minerAddr) 267 268 store := adt.AsStore(rt) 269 var mmap *adt.Multimap 270 var err error 271 if st.ProofValidationBatch == nil { 272 mmap, err = adt.MakeEmptyMultimap(store, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) 273 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to create empty proof validation set") 274 } else { 275 mmap, err = adt.AsMultimap(adt.AsStore(rt), *st.ProofValidationBatch, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) 276 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof batch set") 277 } 278 279 arr, found, err := mmap.Get(abi.AddrKey(minerAddr)) 280 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get get seal verify infos at addr %s", minerAddr) 281 if found && arr.Length() >= MaxMinerProveCommitsPerEpoch { 282 rt.Abortf(ErrTooManyProveCommits, "miner %s attempting to prove commit over %d sectors in epoch", minerAddr, MaxMinerProveCommitsPerEpoch) 283 } 284 285 err = mmap.Add(abi.AddrKey(minerAddr), sealInfo) 286 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to insert proof into batch") 287 288 mmrc, err := mmap.Root() 289 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush proof batch") 290 291 rt.ChargeGas("OnSubmitVerifySeal", GasOnSubmitVerifySeal, 0) 292 st.ProofValidationBatch = &mmrc 293 }) 294 295 return nil 296 } 297 298 // Changed since v0: 299 // - QualityAdjPowerSmoothed is not a pointer 300 type CurrentTotalPowerReturn struct { 301 RawBytePower abi.StoragePower 302 QualityAdjPower abi.StoragePower 303 PledgeCollateral abi.TokenAmount 304 QualityAdjPowerSmoothed smoothing.FilterEstimate 305 } 306 307 // Returns the total power and pledge recorded by the power actor. 308 // The returned values are frozen during the cron tick before this epoch 309 // so that this method returns consistent values while processing all messages 310 // of an epoch. 311 func (a Actor) CurrentTotalPower(rt Runtime, _ *abi.EmptyValue) *CurrentTotalPowerReturn { 312 rt.ValidateImmediateCallerAcceptAny() 313 var st State 314 rt.StateReadonly(&st) 315 316 return &CurrentTotalPowerReturn{ 317 RawBytePower: st.ThisEpochRawBytePower, 318 QualityAdjPower: st.ThisEpochQualityAdjPower, 319 PledgeCollateral: st.ThisEpochPledgeCollateral, 320 QualityAdjPowerSmoothed: st.ThisEpochQAPowerSmoothed, 321 } 322 } 323 324 //////////////////////////////////////////////////////////////////////////////// 325 // Method utility functions 326 //////////////////////////////////////////////////////////////////////////////// 327 328 func validateMinerHasClaim(rt Runtime, st State, minerAddr addr.Address) { 329 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 330 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 331 332 found, err := claims.Has(abi.AddrKey(minerAddr)) 333 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim") 334 if !found { 335 rt.Abortf(exitcode.ErrForbidden, "unknown miner %s forbidden to interact with power actor", minerAddr) 336 } 337 } 338 339 func (a Actor) processBatchProofVerifies(rt Runtime) { 340 var st State 341 342 var miners []addr.Address 343 verifies := make(map[addr.Address][]proof.SealVerifyInfo) 344 345 rt.StateTransaction(&st, func() { 346 store := adt.AsStore(rt) 347 if st.ProofValidationBatch == nil { 348 return 349 } 350 mmap, err := adt.AsMultimap(store, *st.ProofValidationBatch, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) 351 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proofs validation batch") 352 353 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 354 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 355 356 err = mmap.ForAll(func(k string, arr *adt.Array) error { 357 a, err := addr.NewFromBytes([]byte(k)) 358 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to parse address key") 359 360 // refuse to process proofs for miner with no claim 361 found, err := claims.Has(abi.AddrKey(a)) 362 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim") 363 if !found { 364 rt.Log(rtt.WARN, "skipping batch verifies for unknown miner %s", a) 365 return nil 366 } 367 368 miners = append(miners, a) 369 370 var infos []proof.SealVerifyInfo 371 var svi proof.SealVerifyInfo 372 err = arr.ForEach(&svi, func(i int64) error { 373 infos = append(infos, svi) 374 return nil 375 }) 376 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to iterate over proof verify array for miner %s", a) 377 378 verifies[a] = infos 379 return nil 380 }) 381 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to iterate proof batch") 382 383 st.ProofValidationBatch = nil 384 }) 385 386 res, err := rt.BatchVerifySeals(verifies) 387 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to batch verify") 388 389 for _, m := range miners { 390 vres, ok := res[m] 391 if !ok { 392 rt.Abortf(exitcode.ErrNotFound, "batch verify seals syscall implemented incorrectly") 393 } 394 395 verifs := verifies[m] 396 397 seen := map[abi.SectorNumber]struct{}{} 398 var successful []abi.SectorNumber 399 for i, r := range vres { 400 if r { 401 snum := verifs[i].SectorID.Number 402 403 if _, exists := seen[snum]; exists { 404 // filter-out duplicates 405 continue 406 } 407 408 seen[snum] = struct{}{} 409 successful = append(successful, snum) 410 } 411 } 412 413 if len(successful) > 0 { 414 // The exit code is explicitly ignored 415 _ = rt.Send( 416 m, 417 builtin.MethodsMiner.ConfirmSectorProofsValid, 418 &builtin.ConfirmSectorProofsParams{Sectors: successful}, 419 abi.NewTokenAmount(0), 420 &builtin.Discard{}, 421 ) 422 } 423 } 424 } 425 426 func (a Actor) processDeferredCronEvents(rt Runtime) { 427 rtEpoch := rt.CurrEpoch() 428 429 var cronEvents []CronEvent 430 var st State 431 rt.StateTransaction(&st, func() { 432 events, err := adt.AsMultimap(adt.AsStore(rt), st.CronEventQueue, CronQueueHamtBitwidth, CronQueueAmtBitwidth) 433 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events") 434 435 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 436 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 437 438 for epoch := st.FirstCronEpoch; epoch <= rtEpoch; epoch++ { 439 epochEvents, err := loadCronEvents(events, epoch) 440 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load cron events at %v", epoch) 441 442 for _, evt := range epochEvents { 443 // refuse to process proofs for miner with no claim 444 found, err := claims.Has(abi.AddrKey(evt.MinerAddr)) 445 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to look up claim") 446 if !found { 447 rt.Log(rtt.WARN, "skipping cron event for unknown miner %v", evt.MinerAddr) 448 continue 449 } 450 cronEvents = append(cronEvents, evt) 451 } 452 453 if len(epochEvents) > 0 { 454 err = events.RemoveAll(epochKey(epoch)) 455 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to clear cron events at %v", epoch) 456 } 457 } 458 459 st.FirstCronEpoch = rtEpoch + 1 460 461 st.CronEventQueue, err = events.Root() 462 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush events") 463 }) 464 failedMinerCrons := make([]addr.Address, 0) 465 for _, event := range cronEvents { 466 code := rt.Send( 467 event.MinerAddr, 468 builtin.MethodsMiner.OnDeferredCronEvent, 469 builtin.CBORBytes(event.CallbackPayload), 470 abi.NewTokenAmount(0), 471 &builtin.Discard{}, 472 ) 473 // If a callback fails, this actor continues to invoke other callbacks 474 // and persists state removing the failed event from the event queue. It won't be tried again. 475 // Failures are unexpected here but will result in removal of miner power 476 // A log message would really help here. 477 if code != exitcode.Ok { 478 rt.Log(rtt.WARN, "OnDeferredCronEvent failed for miner %s: exitcode %d", event.MinerAddr, code) 479 failedMinerCrons = append(failedMinerCrons, event.MinerAddr) 480 } 481 } 482 483 if len(failedMinerCrons) > 0 { 484 rt.StateTransaction(&st, func() { 485 claims, err := adt.AsMap(adt.AsStore(rt), st.Claims, builtin.DefaultHamtBitwidth) 486 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load claims") 487 488 // Remove miner claim and leave miner frozen 489 for _, minerAddr := range failedMinerCrons { 490 found, err := st.deleteClaim(claims, minerAddr) 491 if err != nil { 492 rt.Log(rtt.ERROR, "failed to delete claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err) 493 continue 494 } else if !found { 495 rt.Log(rtt.ERROR, "can't find claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err) 496 continue 497 } 498 499 // Decrement miner count to keep stats consistent. 500 st.MinerCount-- 501 } 502 503 st.Claims, err = claims.Root() 504 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush claims") 505 }) 506 } 507 }