github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/market/market_actor.go (about) 1 package market 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "sort" 7 8 addr "github.com/filecoin-project/go-address" 9 "github.com/filecoin-project/go-state-types/abi" 10 "github.com/filecoin-project/go-state-types/big" 11 "github.com/filecoin-project/go-state-types/cbor" 12 "github.com/filecoin-project/go-state-types/crypto" 13 "github.com/filecoin-project/go-state-types/exitcode" 14 rtt "github.com/filecoin-project/go-state-types/rt" 15 market0 "github.com/filecoin-project/specs-actors/actors/builtin/market" 16 "github.com/ipfs/go-cid" 17 cbg "github.com/whyrusleeping/cbor-gen" 18 "golang.org/x/xerrors" 19 20 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 21 "github.com/filecoin-project/specs-actors/v4/actors/builtin/power" 22 "github.com/filecoin-project/specs-actors/v4/actors/builtin/reward" 23 "github.com/filecoin-project/specs-actors/v4/actors/builtin/verifreg" 24 "github.com/filecoin-project/specs-actors/v4/actors/runtime" 25 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 26 ) 27 28 type Actor struct{} 29 30 type Runtime = runtime.Runtime 31 32 func (a Actor) Exports() []interface{} { 33 return []interface{}{ 34 builtin.MethodConstructor: a.Constructor, 35 2: a.AddBalance, 36 3: a.WithdrawBalance, 37 4: a.PublishStorageDeals, 38 5: a.VerifyDealsForActivation, 39 6: a.ActivateDeals, 40 7: a.OnMinerSectorsTerminate, 41 8: a.ComputeDataCommitment, 42 9: a.CronTick, 43 } 44 } 45 46 func (a Actor) Code() cid.Cid { 47 return builtin.StorageMarketActorCodeID 48 } 49 50 func (a Actor) IsSingleton() bool { 51 return true 52 } 53 54 func (a Actor) State() cbor.Er { 55 return new(State) 56 } 57 58 var _ runtime.VMActor = Actor{} 59 60 //////////////////////////////////////////////////////////////////////////////// 61 // Actor methods 62 //////////////////////////////////////////////////////////////////////////////// 63 64 func (a Actor) Constructor(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue { 65 rt.ValidateImmediateCallerIs(builtin.SystemActorAddr) 66 67 st, err := ConstructState(adt.AsStore(rt)) 68 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to create state") 69 rt.StateCreate(st) 70 return nil 71 } 72 73 //type WithdrawBalanceParams struct { 74 // ProviderOrClientAddress addr.Address 75 // Amount abi.TokenAmount 76 //} 77 type WithdrawBalanceParams = market0.WithdrawBalanceParams 78 79 // Attempt to withdraw the specified amount from the balance held in escrow. 80 // If less than the specified amount is available, yields the entire available balance. 81 func (a Actor) WithdrawBalance(rt Runtime, params *WithdrawBalanceParams) *abi.EmptyValue { 82 if params.Amount.LessThan(big.Zero()) { 83 rt.Abortf(exitcode.ErrIllegalArgument, "negative amount %v", params.Amount) 84 } 85 86 nominal, recipient, approvedCallers := escrowAddress(rt, params.ProviderOrClientAddress) 87 // for providers -> only corresponding owner or worker can withdraw 88 // for clients -> only the client i.e the recipient can withdraw 89 rt.ValidateImmediateCallerIs(approvedCallers...) 90 91 amountExtracted := abi.NewTokenAmount(0) 92 var st State 93 rt.StateTransaction(&st, func() { 94 msm, err := st.mutator(adt.AsStore(rt)).withEscrowTable(WritePermission). 95 withLockedTable(WritePermission).build() 96 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") 97 98 // The withdrawable amount might be slightly less than nominal 99 // depending on whether or not all relevant entries have been processed 100 // by cron 101 minBalance, err := msm.lockedTable.Get(nominal) 102 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get locked balance") 103 104 ex, err := msm.escrowTable.SubtractWithMinimum(nominal, params.Amount, minBalance) 105 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to subtract from escrow table") 106 107 err = msm.commitState() 108 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 109 110 amountExtracted = ex 111 }) 112 113 code := rt.Send(recipient, builtin.MethodSend, nil, amountExtracted, &builtin.Discard{}) 114 builtin.RequireSuccess(rt, code, "failed to send funds") 115 return nil 116 } 117 118 // Deposits the received value into the balance held in escrow. 119 func (a Actor) AddBalance(rt Runtime, providerOrClientAddress *addr.Address) *abi.EmptyValue { 120 msgValue := rt.ValueReceived() 121 builtin.RequireParam(rt, msgValue.GreaterThan(big.Zero()), "balance to add must be greater than zero") 122 123 // only signing parties can add balance for client AND provider. 124 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 125 126 nominal, _, _ := escrowAddress(rt, *providerOrClientAddress) 127 128 var st State 129 rt.StateTransaction(&st, func() { 130 msm, err := st.mutator(adt.AsStore(rt)).withEscrowTable(WritePermission). 131 withLockedTable(WritePermission).build() 132 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") 133 134 err = msm.escrowTable.Add(nominal, msgValue) 135 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to add balance to escrow table") 136 137 err = msm.commitState() 138 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 139 }) 140 return nil 141 } 142 143 //type PublishStorageDealsParams struct { 144 // Deals []ClientDealProposal 145 //} 146 type PublishStorageDealsParams = market0.PublishStorageDealsParams 147 148 //type PublishStorageDealsReturn struct { 149 // IDs []abi.DealID 150 //} 151 type PublishStorageDealsReturn = market0.PublishStorageDealsReturn 152 153 // Publish a new set of storage deals (not yet included in a sector). 154 func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams) *PublishStorageDealsReturn { 155 156 // Deal message must have a From field identical to the provider of all the deals. 157 // This allows us to retain and verify only the client's signature in each deal proposal itself. 158 rt.ValidateImmediateCallerType(builtin.CallerTypesSignable...) 159 if len(params.Deals) == 0 { 160 rt.Abortf(exitcode.ErrIllegalArgument, "empty deals parameter") 161 } 162 163 // All deals should have the same provider so get worker once 164 providerRaw := params.Deals[0].Proposal.Provider 165 provider, ok := rt.ResolveAddress(providerRaw) 166 if !ok { 167 rt.Abortf(exitcode.ErrNotFound, "failed to resolve provider address %v", providerRaw) 168 } 169 170 codeID, ok := rt.GetActorCodeCID(provider) 171 builtin.RequireParam(rt, ok, "no codeId for address %v", provider) 172 if !codeID.Equals(builtin.StorageMinerActorCodeID) { 173 rt.Abortf(exitcode.ErrIllegalArgument, "deal provider is not a StorageMinerActor") 174 } 175 176 caller := rt.Caller() 177 _, worker, controllers := builtin.RequestMinerControlAddrs(rt, provider) 178 callerOk := caller == worker 179 for _, controller := range controllers { 180 if callerOk { 181 break 182 } 183 callerOk = caller == controller 184 } 185 if !callerOk { 186 rt.Abortf(exitcode.ErrForbidden, "caller %v is not worker or control address of provider %v", caller, provider) 187 } 188 189 resolvedAddrs := make(map[addr.Address]addr.Address, len(params.Deals)) 190 baselinePower := requestCurrentBaselinePower(rt) 191 networkRawPower, networkQAPower := requestCurrentNetworkPower(rt) 192 193 var newDealIds []abi.DealID 194 var st State 195 rt.StateTransaction(&st, func() { 196 msm, err := st.mutator(adt.AsStore(rt)).withPendingProposals(WritePermission). 197 withDealProposals(WritePermission).withDealsByEpoch(WritePermission).withEscrowTable(WritePermission). 198 withLockedTable(WritePermission).build() 199 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") 200 201 // All storage dealProposals will be added in an atomic transaction; this operation will be unrolled if any of them fails. 202 for di, deal := range params.Deals { 203 validateDeal(rt, deal, networkRawPower, networkQAPower, baselinePower) 204 205 if deal.Proposal.Provider != provider && deal.Proposal.Provider != providerRaw { 206 rt.Abortf(exitcode.ErrIllegalArgument, "cannot publish deals from different providers at the same time") 207 } 208 209 client, ok := rt.ResolveAddress(deal.Proposal.Client) 210 if !ok { 211 rt.Abortf(exitcode.ErrNotFound, "failed to resolve client address %v", deal.Proposal.Client) 212 } 213 // Normalise provider and client addresses in the proposal stored on chain (after signature verification). 214 deal.Proposal.Provider = provider 215 resolvedAddrs[deal.Proposal.Client] = client 216 deal.Proposal.Client = client 217 218 err := msm.lockClientAndProviderBalances(&deal.Proposal) 219 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to lock balance") 220 221 id := msm.generateStorageDealID() 222 223 pcid, err := deal.Proposal.Cid() 224 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalArgument, "failed to take cid of proposal %d", di) 225 226 has, err := msm.pendingDeals.Has(abi.CidKey(pcid)) 227 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check for existence of deal proposal") 228 if has { 229 rt.Abortf(exitcode.ErrIllegalArgument, "cannot publish duplicate deals") 230 } 231 232 err = msm.pendingDeals.Put(abi.CidKey(pcid)) 233 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set pending deal") 234 235 err = msm.dealProposals.Set(id, &deal.Proposal) 236 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal") 237 238 // We should randomize the first epoch for when the deal will be processed so an attacker isn't able to 239 // schedule too many deals for the same tick. 240 processEpoch, err := genRandNextEpoch(rt.CurrEpoch(), &deal.Proposal, rt.GetRandomnessFromBeacon) 241 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to generate random process epoch") 242 243 err = msm.dealsByEpoch.Put(processEpoch, id) 244 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal ops by epoch") 245 246 newDealIds = append(newDealIds, id) 247 } 248 249 err = msm.commitState() 250 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 251 }) 252 253 for _, deal := range params.Deals { 254 // Check VerifiedClient allowed cap and deduct PieceSize from cap. 255 // Either the DealSize is within the available DataCap of the VerifiedClient 256 // or this message will fail. We do not allow a deal that is partially verified. 257 if deal.Proposal.VerifiedDeal { 258 resolvedClient, ok := resolvedAddrs[deal.Proposal.Client] 259 builtin.RequireParam(rt, ok, "could not get resolvedClient client address") 260 261 code := rt.Send( 262 builtin.VerifiedRegistryActorAddr, 263 builtin.MethodsVerifiedRegistry.UseBytes, 264 &verifreg.UseBytesParams{ 265 Address: resolvedClient, 266 DealSize: big.NewIntUnsigned(uint64(deal.Proposal.PieceSize)), 267 }, 268 abi.NewTokenAmount(0), 269 &builtin.Discard{}, 270 ) 271 builtin.RequireSuccess(rt, code, "failed to add verified deal for client: %v", deal.Proposal.Client) 272 } 273 } 274 275 return &PublishStorageDealsReturn{IDs: newDealIds} 276 } 277 278 // Changed since v2: 279 // - Array of sectors rather than just one 280 // - Removed SectorStart (which is unknown at call time) 281 type VerifyDealsForActivationParams struct { 282 Sectors []SectorDeals 283 } 284 285 type SectorDeals struct { 286 SectorExpiry abi.ChainEpoch 287 DealIDs []abi.DealID 288 } 289 290 // Changed since v2: 291 // - Array of sectors weights 292 type VerifyDealsForActivationReturn struct { 293 Sectors []SectorWeights 294 } 295 296 type SectorWeights struct { 297 DealSpace uint64 // Total space in bytes of submitted deals. 298 DealWeight abi.DealWeight // Total space*time of submitted deals. 299 VerifiedDealWeight abi.DealWeight // Total space*time of submitted verified deals. 300 } 301 302 // Computes the weight of deals proposed for inclusion in a number of sectors. 303 // Deal weight is defined as the sum, over all deals in the set, of the product of deal size and duration. 304 // 305 // This method performs some light validation on the way in order to fail early if deals can be 306 // determined to be invalid for the proposed sector properties. 307 // Full deal validation is deferred to deal activation since it depends on the activation epoch. 308 func (a Actor) VerifyDealsForActivation(rt Runtime, params *VerifyDealsForActivationParams) *VerifyDealsForActivationReturn { 309 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 310 minerAddr := rt.Caller() 311 currEpoch := rt.CurrEpoch() 312 313 var st State 314 rt.StateReadonly(&st) 315 store := adt.AsStore(rt) 316 317 proposals, err := AsDealProposalArray(store, st.Proposals) 318 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deal proposals") 319 320 weights := make([]SectorWeights, len(params.Sectors)) 321 for i, sector := range params.Sectors { 322 // Pass the current epoch as the activation epoch for validation. 323 // The sector activation epoch isn't yet known, but it's still more helpful to fail now if the deal 324 // is so late that a sector activating now couldn't include it. 325 dealWeight, verifiedWeight, dealSpace, err := validateAndComputeDealWeight(proposals, sector.DealIDs, minerAddr, sector.SectorExpiry, currEpoch) 326 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to validate deal proposals for activation") 327 328 weights[i] = SectorWeights{ 329 DealSpace: dealSpace, 330 DealWeight: dealWeight, 331 VerifiedDealWeight: verifiedWeight, 332 } 333 } 334 335 return &VerifyDealsForActivationReturn{ 336 Sectors: weights, 337 } 338 } 339 340 //type ActivateDealsParams struct { 341 // DealIDs []abi.DealID 342 // SectorExpiry abi.ChainEpoch 343 //} 344 type ActivateDealsParams = market0.ActivateDealsParams 345 346 // Verify that a given set of storage deals is valid for a sector currently being ProveCommitted, 347 // update the market's internal state accordingly. 348 func (a Actor) ActivateDeals(rt Runtime, params *ActivateDealsParams) *abi.EmptyValue { 349 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 350 minerAddr := rt.Caller() 351 currEpoch := rt.CurrEpoch() 352 353 var st State 354 store := adt.AsStore(rt) 355 356 // Update deal dealStates. 357 rt.StateTransaction(&st, func() { 358 _, _, _, err := ValidateDealsForActivation(&st, store, params.DealIDs, minerAddr, params.SectorExpiry, currEpoch) 359 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to validate dealProposals for activation") 360 361 msm, err := st.mutator(adt.AsStore(rt)).withDealStates(WritePermission). 362 withPendingProposals(ReadOnlyPermission).withDealProposals(ReadOnlyPermission).build() 363 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") 364 365 for _, dealID := range params.DealIDs { 366 // This construction could be replaced with a single "update deal state" state method, possibly batched 367 // over all deal ids at once. 368 _, found, err := msm.dealStates.Get(dealID) 369 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get state for dealId %d", dealID) 370 if found { 371 rt.Abortf(exitcode.ErrIllegalArgument, "deal %d already included in another sector", dealID) 372 } 373 374 proposal, err := getDealProposal(msm.dealProposals, dealID) 375 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get dealId %d", dealID) 376 377 propc, err := proposal.Cid() 378 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to calculate proposal CID") 379 380 has, err := msm.pendingDeals.Has(abi.CidKey(propc)) 381 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get pending proposal %v", propc) 382 383 if !has { 384 rt.Abortf(exitcode.ErrIllegalState, "tried to activate deal that was not in the pending set (%s)", propc) 385 } 386 387 err = msm.dealStates.Set(dealID, &DealState{ 388 SectorStartEpoch: currEpoch, 389 LastUpdatedEpoch: epochUndefined, 390 SlashEpoch: epochUndefined, 391 }) 392 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal state %d", dealID) 393 } 394 395 err = msm.commitState() 396 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 397 }) 398 399 return nil 400 } 401 402 //type ComputeDataCommitmentParams struct { 403 // DealIDs []abi.DealID 404 // SectorType abi.RegisteredSealProof 405 //} 406 type ComputeDataCommitmentParams = market0.ComputeDataCommitmentParams 407 408 func (a Actor) ComputeDataCommitment(rt Runtime, params *ComputeDataCommitmentParams) *cbg.CborCid { 409 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 410 411 var st State 412 rt.StateReadonly(&st) 413 proposals, err := AsDealProposalArray(adt.AsStore(rt), st.Proposals) 414 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deal dealProposals") 415 416 pieces := make([]abi.PieceInfo, 0) 417 for _, dealID := range params.DealIDs { 418 deal, err := getDealProposal(proposals, dealID) 419 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get dealId %d", dealID) 420 421 pieces = append(pieces, abi.PieceInfo{ 422 PieceCID: deal.PieceCID, 423 Size: deal.PieceSize, 424 }) 425 } 426 427 commd, err := rt.ComputeUnsealedSectorCID(params.SectorType, pieces) 428 if err != nil { 429 rt.Abortf(exitcode.ErrIllegalArgument, "failed to compute unsealed sector CID: %s", err) 430 } 431 432 return (*cbg.CborCid)(&commd) 433 } 434 435 //type OnMinerSectorsTerminateParams struct { 436 // Epoch abi.ChainEpoch 437 // DealIDs []abi.DealID 438 //} 439 type OnMinerSectorsTerminateParams = market0.OnMinerSectorsTerminateParams 440 441 // Terminate a set of deals in response to their containing sector being terminated. 442 // Slash provider collateral, refund client collateral, and refund partial unpaid escrow 443 // amount to client. 444 func (a Actor) OnMinerSectorsTerminate(rt Runtime, params *OnMinerSectorsTerminateParams) *abi.EmptyValue { 445 rt.ValidateImmediateCallerType(builtin.StorageMinerActorCodeID) 446 minerAddr := rt.Caller() 447 448 var st State 449 rt.StateTransaction(&st, func() { 450 msm, err := st.mutator(adt.AsStore(rt)).withDealStates(WritePermission). 451 withDealProposals(ReadOnlyPermission).build() 452 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load deal state") 453 454 for _, dealID := range params.DealIDs { 455 deal, found, err := msm.dealProposals.Get(dealID) 456 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get deal proposal %v", dealID) 457 // deal could have terminated and hence deleted before the sector is terminated. 458 // we should simply continue instead of aborting execution here if a deal is not found. 459 if !found { 460 continue 461 } 462 builtin.RequireState(rt, deal.Provider == minerAddr, "caller %v is not the provider %v of deal %v", 463 minerAddr, deal.Provider, dealID) 464 465 // do not slash expired deals 466 if deal.EndEpoch <= params.Epoch { 467 continue 468 } 469 470 state, found, err := msm.dealStates.Get(dealID) 471 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get deal state %v", dealID) 472 if !found { 473 rt.Abortf(exitcode.ErrIllegalArgument, "no state for deal %v", dealID) 474 } 475 476 // if a deal is already slashed, we don't need to do anything here. 477 if state.SlashEpoch != epochUndefined { 478 continue 479 } 480 481 // mark the deal for slashing here. 482 // actual releasing of locked funds for the client and slashing of provider collateral happens in CronTick. 483 state.SlashEpoch = params.Epoch 484 485 err = msm.dealStates.Set(dealID, state) 486 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal state %v", dealID) 487 } 488 489 err = msm.commitState() 490 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 491 }) 492 return nil 493 } 494 495 func (a Actor) CronTick(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue { 496 rt.ValidateImmediateCallerIs(builtin.CronActorAddr) 497 amountSlashed := big.Zero() 498 499 var timedOutVerifiedDeals []*DealProposal 500 501 var st State 502 rt.StateTransaction(&st, func() { 503 updatesNeeded := make(map[abi.ChainEpoch][]abi.DealID) 504 505 msm, err := st.mutator(adt.AsStore(rt)).withDealStates(WritePermission). 506 withLockedTable(WritePermission).withEscrowTable(WritePermission).withDealsByEpoch(WritePermission). 507 withDealProposals(WritePermission).withPendingProposals(WritePermission).build() 508 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load state") 509 510 for i := st.LastCron + 1; i <= rt.CurrEpoch(); i++ { 511 err = msm.dealsByEpoch.ForEach(i, func(dealID abi.DealID) error { 512 deal, err := getDealProposal(msm.dealProposals, dealID) 513 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get dealId %d", dealID) 514 515 dcid, err := deal.Cid() 516 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to calculate CID for proposal %v", dealID) 517 518 state, found, err := msm.dealStates.Get(dealID) 519 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get deal state") 520 521 // deal has been published but not activated yet -> terminate it as it has timed out 522 if !found { 523 // Not yet appeared in proven sector; check for timeout. 524 builtin.RequireState(rt, rt.CurrEpoch() >= deal.StartEpoch, "deal %d processed before start epoch %d", 525 dealID, deal.StartEpoch) 526 527 slashed := msm.processDealInitTimedOut(rt, deal) 528 if !slashed.IsZero() { 529 amountSlashed = big.Add(amountSlashed, slashed) 530 } 531 if deal.VerifiedDeal { 532 timedOutVerifiedDeals = append(timedOutVerifiedDeals, deal) 533 } 534 535 // we should not attempt to delete the DealState because it does NOT exist 536 if err := deleteDealProposalAndState(dealID, msm.dealStates, msm.dealProposals, true, false); err != nil { 537 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete deal %d", dealID) 538 } 539 540 pdErr := msm.pendingDeals.Delete(abi.CidKey(dcid)) 541 builtin.RequireNoErr(rt, pdErr, exitcode.ErrIllegalState, "failed to delete pending proposal %v", dcid) 542 return nil 543 } 544 545 // if this is the first cron tick for the deal, it should be in the pending state. 546 if state.LastUpdatedEpoch == epochUndefined { 547 pdErr := msm.pendingDeals.Delete(abi.CidKey(dcid)) 548 builtin.RequireNoErr(rt, pdErr, exitcode.ErrIllegalState, "failed to delete pending proposal %v", dcid) 549 } 550 551 slashAmount, nextEpoch, removeDeal := msm.updatePendingDealState(rt, state, deal, rt.CurrEpoch()) 552 builtin.RequireState(rt, slashAmount.GreaterThanEqual(big.Zero()), "computed negative slash amount %v for deal %d", slashAmount, dealID) 553 554 if removeDeal { 555 builtin.RequireState(rt, nextEpoch == epochUndefined, "removed deal %d should have no scheduled epoch (got %d)", dealID, nextEpoch) 556 557 amountSlashed = big.Add(amountSlashed, slashAmount) 558 err := deleteDealProposalAndState(dealID, msm.dealStates, msm.dealProposals, true, true) 559 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete deal proposal and states") 560 } else { 561 builtin.RequireState(rt, nextEpoch > rt.CurrEpoch(), "continuing deal %d next epoch %d should be in future", dealID, nextEpoch) 562 builtin.RequireState(rt, slashAmount.IsZero(), "continuing deal %d should not be slashed", dealID) 563 564 // Update deal's LastUpdatedEpoch in DealStates 565 state.LastUpdatedEpoch = rt.CurrEpoch() 566 err = msm.dealStates.Set(dealID, state) 567 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to set deal state") 568 569 updatesNeeded[nextEpoch] = append(updatesNeeded[nextEpoch], dealID) 570 } 571 572 return nil 573 }) 574 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to iterate deal ops") 575 576 err = msm.dealsByEpoch.RemoveAll(i) 577 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete deal ops for epoch %v", i) 578 } 579 580 // Iterate changes in sorted order to ensure that loads/stores 581 // are deterministic. Otherwise, we could end up charging an 582 // inconsistent amount of gas. 583 changedEpochs := make([]abi.ChainEpoch, 0, len(updatesNeeded)) 584 for epoch := range updatesNeeded { //nolint:nomaprange 585 changedEpochs = append(changedEpochs, epoch) 586 } 587 588 sort.Slice(changedEpochs, func(i, j int) bool { return changedEpochs[i] < changedEpochs[j] }) 589 590 for _, epoch := range changedEpochs { 591 err = msm.dealsByEpoch.PutMany(epoch, updatesNeeded[epoch]) 592 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to reinsert deal IDs for epoch %v", epoch) 593 } 594 595 st.LastCron = rt.CurrEpoch() 596 597 err = msm.commitState() 598 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush state") 599 }) 600 601 for _, d := range timedOutVerifiedDeals { 602 code := rt.Send( 603 builtin.VerifiedRegistryActorAddr, 604 builtin.MethodsVerifiedRegistry.RestoreBytes, 605 &verifreg.RestoreBytesParams{ 606 Address: d.Client, 607 DealSize: big.NewIntUnsigned(uint64(d.PieceSize)), 608 }, 609 abi.NewTokenAmount(0), 610 &builtin.Discard{}, 611 ) 612 613 if !code.IsSuccess() { 614 rt.Log(rtt.ERROR, "failed to send RestoreBytes call to the VerifReg actor for timed-out verified deal, client: %s, dealSize: %v, "+ 615 "provider: %v, got code %v", d.Client, d.PieceSize, d.Provider, code) 616 } 617 } 618 619 if !amountSlashed.IsZero() { 620 e := rt.Send(builtin.BurntFundsActorAddr, builtin.MethodSend, nil, amountSlashed, &builtin.Discard{}) 621 builtin.RequireSuccess(rt, e, "expected send to burnt funds actor to succeed") 622 } 623 624 return nil 625 } 626 627 func genRandNextEpoch(currEpoch abi.ChainEpoch, deal *DealProposal, rbF func(crypto.DomainSeparationTag, abi.ChainEpoch, []byte) abi.Randomness) (abi.ChainEpoch, error) { 628 buf := bytes.Buffer{} 629 if err := deal.MarshalCBOR(&buf); err != nil { 630 return epochUndefined, xerrors.Errorf("failed to marshal proposal: %w", err) 631 } 632 633 rb := rbF(crypto.DomainSeparationTag_MarketDealCronSeed, currEpoch-1, buf.Bytes()) 634 635 // generate a random epoch in [baseEpoch, baseEpoch + DealUpdatesInterval) 636 offset := binary.BigEndian.Uint64(rb) 637 638 return deal.StartEpoch + abi.ChainEpoch(offset%uint64(DealUpdatesInterval)), nil 639 } 640 641 func deleteDealProposalAndState(dealId abi.DealID, states *DealMetaArray, proposals *DealArray, removeProposal bool, 642 removeState bool) error { 643 if removeProposal { 644 if err := proposals.Delete(dealId); err != nil { 645 return xerrors.Errorf("failed to delete proposal %d : %w", dealId, err) 646 } 647 } 648 649 if removeState { 650 if err := states.Delete(dealId); err != nil { 651 return xerrors.Errorf("failed to delete deal state: %w", err) 652 } 653 } 654 655 return nil 656 } 657 658 // 659 // Exported functions 660 // 661 662 // Validates a collection of deal dealProposals for activation, and returns their combined weight, 663 // split into regular deal weight and verified deal weight. 664 func ValidateDealsForActivation( 665 st *State, store adt.Store, dealIDs []abi.DealID, minerAddr addr.Address, sectorExpiry, currEpoch abi.ChainEpoch, 666 ) (big.Int, big.Int, uint64, error) { 667 proposals, err := AsDealProposalArray(store, st.Proposals) 668 if err != nil { 669 return big.Int{}, big.Int{}, 0, xerrors.Errorf("failed to load dealProposals: %w", err) 670 } 671 672 return validateAndComputeDealWeight(proposals, dealIDs, minerAddr, sectorExpiry, currEpoch) 673 } 674 675 //////////////////////////////////////////////////////////////////////////////// 676 // Checks 677 //////////////////////////////////////////////////////////////////////////////// 678 679 func validateAndComputeDealWeight(proposals *DealArray, dealIDs []abi.DealID, minerAddr addr.Address, 680 sectorExpiry abi.ChainEpoch, sectorActivation abi.ChainEpoch) (big.Int, big.Int, uint64, error) { 681 682 seenDealIDs := make(map[abi.DealID]struct{}, len(dealIDs)) 683 totalDealSpace := uint64(0) 684 totalDealSpaceTime := big.Zero() 685 totalVerifiedSpaceTime := big.Zero() 686 for _, dealID := range dealIDs { 687 // Make sure we don't double-count deals. 688 if _, seen := seenDealIDs[dealID]; seen { 689 return big.Int{}, big.Int{}, 0, exitcode.ErrIllegalArgument.Wrapf("deal ID %d present multiple times", dealID) 690 } 691 seenDealIDs[dealID] = struct{}{} 692 693 proposal, found, err := proposals.Get(dealID) 694 if err != nil { 695 return big.Int{}, big.Int{}, 0, xerrors.Errorf("failed to load deal %d: %w", dealID, err) 696 } 697 if !found { 698 return big.Int{}, big.Int{}, 0, exitcode.ErrNotFound.Wrapf("no such deal %d", dealID) 699 } 700 if err = validateDealCanActivate(proposal, minerAddr, sectorExpiry, sectorActivation); err != nil { 701 return big.Int{}, big.Int{}, 0, xerrors.Errorf("cannot activate deal %d: %w", dealID, err) 702 } 703 704 // Compute deal weight 705 totalDealSpace += uint64(proposal.PieceSize) 706 dealSpaceTime := DealWeight(proposal) 707 if proposal.VerifiedDeal { 708 totalVerifiedSpaceTime = big.Add(totalVerifiedSpaceTime, dealSpaceTime) 709 } else { 710 totalDealSpaceTime = big.Add(totalDealSpaceTime, dealSpaceTime) 711 } 712 } 713 return totalDealSpaceTime, totalVerifiedSpaceTime, totalDealSpace, nil 714 } 715 716 func validateDealCanActivate(proposal *DealProposal, minerAddr addr.Address, sectorExpiration, sectorActivation abi.ChainEpoch) error { 717 if proposal.Provider != minerAddr { 718 return exitcode.ErrForbidden.Wrapf("proposal has provider %v, must be %v", proposal.Provider, minerAddr) 719 } 720 if sectorActivation > proposal.StartEpoch { 721 return exitcode.ErrIllegalArgument.Wrapf("proposal start epoch %d has already elapsed at %d", proposal.StartEpoch, sectorActivation) 722 } 723 if proposal.EndEpoch > sectorExpiration { 724 return exitcode.ErrIllegalArgument.Wrapf("proposal expiration %d exceeds sector expiration %d", proposal.EndEpoch, sectorExpiration) 725 } 726 return nil 727 } 728 729 func validateDeal(rt Runtime, deal ClientDealProposal, networkRawPower, networkQAPower, baselinePower abi.StoragePower) { 730 if err := dealProposalIsInternallyValid(rt, deal); err != nil { 731 rt.Abortf(exitcode.ErrIllegalArgument, "Invalid deal proposal: %s", err) 732 } 733 734 proposal := deal.Proposal 735 736 if len(proposal.Label) > DealMaxLabelSize { 737 rt.Abortf(exitcode.ErrIllegalArgument, "deal label can be at most %d bytes, is %d", DealMaxLabelSize, len(proposal.Label)) 738 } 739 740 if err := proposal.PieceSize.Validate(); err != nil { 741 rt.Abortf(exitcode.ErrIllegalArgument, "proposal piece size is invalid: %v", err) 742 } 743 744 if !proposal.PieceCID.Defined() { 745 rt.Abortf(exitcode.ErrIllegalArgument, "proposal PieceCID undefined") 746 } 747 748 if proposal.PieceCID.Prefix() != PieceCIDPrefix { 749 rt.Abortf(exitcode.ErrIllegalArgument, "proposal PieceCID had wrong prefix") 750 } 751 752 if proposal.EndEpoch <= proposal.StartEpoch { 753 rt.Abortf(exitcode.ErrIllegalArgument, "proposal end before proposal start") 754 } 755 756 if rt.CurrEpoch() > proposal.StartEpoch { 757 rt.Abortf(exitcode.ErrIllegalArgument, "Deal start epoch has already elapsed.") 758 } 759 760 minDuration, maxDuration := DealDurationBounds(proposal.PieceSize) 761 if proposal.Duration() < minDuration || proposal.Duration() > maxDuration { 762 rt.Abortf(exitcode.ErrIllegalArgument, "Deal duration out of bounds.") 763 } 764 765 minPrice, maxPrice := DealPricePerEpochBounds(proposal.PieceSize, proposal.Duration()) 766 if proposal.StoragePricePerEpoch.LessThan(minPrice) || proposal.StoragePricePerEpoch.GreaterThan(maxPrice) { 767 rt.Abortf(exitcode.ErrIllegalArgument, "Storage price out of bounds.") 768 } 769 770 minProviderCollateral, maxProviderCollateral := DealProviderCollateralBounds(proposal.PieceSize, proposal.VerifiedDeal, 771 networkRawPower, networkQAPower, baselinePower, rt.TotalFilCircSupply()) 772 if proposal.ProviderCollateral.LessThan(minProviderCollateral) || proposal.ProviderCollateral.GreaterThan(maxProviderCollateral) { 773 rt.Abortf(exitcode.ErrIllegalArgument, "Provider collateral out of bounds.") 774 } 775 776 minClientCollateral, maxClientCollateral := DealClientCollateralBounds(proposal.PieceSize, proposal.Duration()) 777 if proposal.ClientCollateral.LessThan(minClientCollateral) || proposal.ClientCollateral.GreaterThan(maxClientCollateral) { 778 rt.Abortf(exitcode.ErrIllegalArgument, "Client collateral out of bounds.") 779 } 780 } 781 782 // 783 // Helpers 784 // 785 786 // Resolves a provider or client address to the canonical form against which a balance should be held, and 787 // the designated recipient address of withdrawals (which is the same, for simple account parties). 788 func escrowAddress(rt Runtime, address addr.Address) (nominal addr.Address, recipient addr.Address, approved []addr.Address) { 789 // Resolve the provided address to the canonical form against which the balance is held. 790 nominal, ok := rt.ResolveAddress(address) 791 if !ok { 792 rt.Abortf(exitcode.ErrIllegalArgument, "failed to resolve address %v", address) 793 } 794 795 codeID, ok := rt.GetActorCodeCID(nominal) 796 if !ok { 797 rt.Abortf(exitcode.ErrIllegalArgument, "no code for address %v", nominal) 798 } 799 800 if codeID.Equals(builtin.StorageMinerActorCodeID) { 801 // Storage miner actor entry; implied funds recipient is the associated owner address. 802 ownerAddr, workerAddr, _ := builtin.RequestMinerControlAddrs(rt, nominal) 803 return nominal, ownerAddr, []addr.Address{ownerAddr, workerAddr} 804 } 805 806 return nominal, nominal, []addr.Address{nominal} 807 } 808 809 func getDealProposal(proposals *DealArray, dealID abi.DealID) (*DealProposal, error) { 810 proposal, found, err := proposals.Get(dealID) 811 if err != nil { 812 return nil, xerrors.Errorf("failed to load proposal: %w", err) 813 } 814 if !found { 815 return nil, exitcode.ErrNotFound.Wrapf("no such deal %d", dealID) 816 } 817 818 return proposal, nil 819 } 820 821 // Requests the current epoch target block reward from the reward actor. 822 func requestCurrentBaselinePower(rt Runtime) abi.StoragePower { 823 var ret reward.ThisEpochRewardReturn 824 code := rt.Send(builtin.RewardActorAddr, builtin.MethodsReward.ThisEpochReward, nil, big.Zero(), &ret) 825 builtin.RequireSuccess(rt, code, "failed to check epoch baseline power") 826 return ret.ThisEpochBaselinePower 827 } 828 829 // Requests the current network total power and pledge from the power actor. 830 func requestCurrentNetworkPower(rt Runtime) (rawPower, qaPower abi.StoragePower) { 831 var pwr power.CurrentTotalPowerReturn 832 code := rt.Send(builtin.StoragePowerActorAddr, builtin.MethodsPower.CurrentTotalPower, nil, big.Zero(), &pwr) 833 builtin.RequireSuccess(rt, code, "failed to check current power") 834 return pwr.RawBytePower, pwr.QualityAdjPower 835 }