changed 5 years ago
Published Linked with GitHub

Actor Spec: StorageMarket

Back to Master Tracking Doc

Contents

At a Glance

The StorageMarket actor manages the relationship between Miners and their Clients. This relationship revolves around DealProposals, in which a Miner agrees to store a specific piece of the Client's data for a period of time and at a specified price.

The StorageMarket actor:

  • Manages Miner and Client collateral held on behalf of their deals.
  • Allows a Miner to publish signed DealProposals, at which point the Miner becomes responsible for the Client's data (as well as the terms of the deal).
    • A Client signs a DealProposal to signal their agreement to its terms
    • A Miner's publication of these signed DealProposals signals the Miner's agreement to its terms
  • Tracks important information about published DealProposals for use at various points during the Miner's Storage Mining Cycle.

Actor Type:

  • Singleton
    • Address: 5

Exported Methods:

  1. Constructor
  2. AddBalance
  3. WithdrawBalance
  4. PublishStorageDeals
  5. VerifyDealsForActivation
  6. ActivateDeals
  7. OnMinerSectorsTerminate
  8. ComputeDataCommitment
  9. CronTick

State

type State struct { Proposals cid.Cid States cid.Cid PendingProposals cid.Cid EscrowTable cid.Cid LockedTable cid.Cid NextID abi.DealID DealOpsByEpoch cid.Cid LastCron abi.ChainEpoch TotalClientLockedCollateral abi.TokenAmount TotalProviderLockedCollateral abi.TokenAmount TotalClientStorageFee abi.TokenAmount }

Proposals: Tracks published DealProposals by unique DealID.

  • Notes:
    • Cid type: AMT, array[DealID]DealProposal
      • Maps a deal's unique DealID to its DealProposal
    • See DealProposal below

States: Tracks important epochs for active deals that have been included in a Sector.

  • Notes:
    • Cid type: AMT, array[DealID]DealState
      • Maps a deal's unique DealID to its DealState
    • See DealState below

PendingProposals: Tracks proposals that have not reached their start epoch. Prevents Miners from publishing the same DealProposal twice.

  • Notes:
    • Cid type, HAMT, map[DealCid]DealProposal
      • Maps a DealProposal Cid to its DealProposal
    • See DealProposal below

EscrowTable: Tracks funds held in escrow. Includes locked funds (see below).

  • Notes:
    • Cid type: BalanceTable, map[Address]TokenAmount
      • Maps an actor's ID address to the token amount held in this table

LockedTable: Tracks funds held in escrow that are also locked.

  • Notes:
    • Cid type: BalanceTable, map[Address]TokenAmount
      • Maps an actor's ID address to the token amount held in this table

NextID: Tracks the next DealID that will be assigned to a published storage deal

  • Invariants:
    • NextID >= 0

DealOpsByEpoch: Tracks deals that need updates at specific epochs. The CronTick method performs these updates and may re-insert the deal if future updates are required.

  • Notes:
    • Cid type: SetMultimap, map[ChainEpoch]Set
      • Maps a StartEpoch to a set of DealProposals that start at that epoch
    • Deals are initially inserted into DealOpsByEpoch in PublishStorageDeals.

LastCron: The last epoch in which CronTick was called. Subsequent CronTicks perform updates from LastCron + 1 to rt.CurrEpoch.

  • Invariants:
    • LastCron >= -1

TotalClientLockedCollateral: Sum of all ClientCollateral locked in published deals. Unlocked when corresponding deals are terminated.

  • Invariants:
    • TotalClientLockedCollateral >= 0

TotalProviderLockedCollateral: Sum of all ProviderCollateral locked in published deals. Unlocked when corresponding deals are terminated.

  • Invariants:
    • TotalProviderLockedCollateral >= 0

TotalClientStorageFee: Sum of all storage fees locked in published deals. Unlocked over time as payments are made.

  • Invariants:
    • TotalClientStorageFee >= 0

DealProposal

A DealProposal contains information about a Storage Deal between a Miner (Provider) and their Client.

type DealProposal struct { PieceCID cid.Cid PieceSize abi.PaddedPieceSize VerifiedDeal bool Client addr.Address Provider addr.Address Label string StartEpoch abi.ChainEpoch EndEpoch abi.ChainEpoch StoragePricePerEpoch abi.TokenAmount ProviderCollateral abi.TokenAmount ClientCollateral abi.TokenAmount }

PieceCID: The root Cid of the "piece" of data to be stored by the Miner on behalf of the Client.

  • Invariants:
    • PieceCID.Defined() == true

PieceSize: The number of bytes that will be stored, padded to a power of two with extra zeroes.

  • Invariants:
    • Should pass PieceSize.Validate() (abi/piece.go)

VerifiedDeal: Whether the deal is a Verified Deal (see VerifiedRegistry spec)

  • Notes:
    • If true, the Client must be a Verified Client. StorageMarket must successfully invoke VerifiedRegistry.UseBytes, consuming a portion of Client's DataCap

Client: The address of the client providing the Piece data and paying the Miner for storage.

  • Notes:
    • Uses ID address protocol

Provider: The Miner storing the data for Client.

  • Notes:
    • Uses ID address protocol
  • Requirements:
    • Must have a Miner actor CodeCID
      • NOTE: This is not currently enforced.

Label: Arbitrary string that can help identify the deal.

StartEpoch: The epoch in which the Provider becomes responsible for storing the data.

EndEpoch: The epoch in which the Provider is no longer responsible for storing the data.

  • Invariants:
    • EndEpoch > StartEpoch

StoragePricePerEpoch: The token amount paid by Client for each epoch the deal is active.

  • Notes:
    • Total payment is calculated for each epoch from StartEpoch to EndEpoch
  • Requirements:
    • StoragePricePerEpoch >= 0

ProviderCollateral: The token amount put up by the deal's Provider. This amount is taken from the Provider's existing entry in st.EscrowTable, and added to the Provider's entry in st.LockedTable.

  • Notes:
    • See OnMinerSectorsTerminate for details on refund/slashing
  • Requirements:
    • ProviderCollateral >= 0

ClientCollateral: The token amount put up by the deal's Client. This amount is taken from the Client's existing entry in st.EscrowTable, and added to the Client's entry in st.LockedTable

  • Notes:
    • See OnMinerSectorsTerminate for details on refund/slashing
  • Requirements:
    • ClientCollateral >= 0

DealState

A deal's DealState tracks important epochs for deals that have been included in a Sector.

type DealState struct { SectorStartEpoch abi.ChainEpoch LastUpdatedEpoch abi.ChainEpoch SlashEpoch abi.ChainEpoch }

SectorStartEpoch: Once a deal is activated (ActivateDeals), the deal's SectorStartEpoch records the StartEpoch in which the Miner ProveCommitted the Sector.

  • Invariants:
    • SectorStartEpoch >= 0

LastUpdatedEpoch: Tracks the last epoch in which a deal's DealState was updated.

  • Invariants:
    • LastUpdatedEpoch >= -1

SlashEpoch: Tracks the epoch in which a deal was slashed.

  • Invariants:
    • SlashEpoch >= -1

Exported Methods

1. Constructor

func (a Actor) Constructor(rt Runtime, _ *adt.EmptyValue) *adt.EmptyValue

Initializes the StorageMarket actor's state. All values are nil, except LastCron, which is set to ChainEpoch(-1).

2. AddBalance

func (a Actor) AddBalance(rt Runtime, providerOrClientAddress *addr.Address) *adt.EmptyValue

Adds ValueReceived() to the EscrowTable entry corresponding to providerOrClientAddress

  • providerOrClientAddress must resolve to an ID address
    • Resolved address must have a CodeCID
      • If CodeCID is of a Miner actor, Caller must be one of the Miner's control addresses
    • Caller must be a Signable-type actor
  • ValueReceived() must be greater than zero

Additionally, an empty entry is created in the LockedTable if one does not exist.

Failure conditions
  • Caller provides zero ValueReceived()
  • Caller is not a Signable-type actor
  • providerOrClientAddress fails to resolve
  • providerOrClientAddress does not have an associated CodeCID
  • providerOrClientAddress resolves to a Miner actor, and Caller is not one of the Miner's control addresses
  • providerOrClientAddress does not resolve to a Miner actor, and Caller is not a Signable-type actor

3. WithdrawBalance

func (a Actor) WithdrawBalance(rt Runtime, params *WithdrawBalanceParams) *adt.EmptyValue

Allows an actor to withdraw available balance held in escrow.

  • If the actor in question is a Miner, balance withdrawn is sent to the Miner's Owner address.
Parameters
type WithdrawBalanceParams struct { ProviderOrClientAddress addr.Address Amount abi.TokenAmount }

ProviderOrClientAddress: Must resolve to a Signable-type actor, or a Miner actor.

  • Notes:
    • All address protocols allowed
  • Requirements:
    • If ProviderOrClientAddress resolves to a Miner actor, Caller must be one of the Miner's control addresses

Amount: The amount to withdraw from escrow.

  • Notes:
    • If less than Amount is available, this method will send the entire available balance.
  • Requirements:
    • Amount >= 0
Failure conditions
  • params.Amount < 0
  • providerOrClientAddress does not have an associated CodeCID
  • providerOrClientAddress resolves to a Miner actor, and Caller is not one of the Miner's control addresses
  • providerOrClientAddress is not a Miner actor, and is not Caller

4. PublishStorageDeals

func (a Actor) PublishStorageDeals(rt Runtime, params *PublishStorageDealsParams) *PublishStorageDealsReturn

Used by a Miner to publish a new set of Storage Deals, each signed by the deal's Client.

  • These deals have not yet been included in a Sector.
  • Each passed-in deal is given a unique DealID before being placed in State:
    • st.PendingProposals: Maps the Cid of the deal's Proposal to the Proposal itself
    • st.Proposals: Maps the unique DealID to the deal's Proposal
    • st.DealOpsByEpoch: Maps the Proposal's StartEpoch to the unique DealID
      • This is used by CronTick
  • Each deal corresponds to collateral amounts that both the Miner and Client must provide.
    • Collateral should already exist for both parties in their respective st.EscrowTable entries.
    • The Client provides a base ClientCollateral, as well as the total storage fee calculated from various deal parameters.
Parameters

PublishStorageDealsParams contains a slice of signed DealProposals.

type PublishStorageDealsParams struct { Deals []ClientDealProposal }

Deals: See ClientDealProposal below

  • Requirements:
    • len(Deals) != 0
ClientDealProposal
type ClientDealProposal struct { Proposal DealProposal ClientSignature acrypto.Signature }

Proposal: The DealProposal agreed to between the Miner and Client

  • Requirements:
    • Must not be nil

ClientSignature: A signature over Proposal by Proposal.Client

  • Requirements:
    • Must not be nil
    • Must be a valid signature as per rt.Syscalls().VerifySignature
Return

A slice of DealIDs corresponding to the newly-published Storage Deals.

type PublishStorageDealsReturn struct { IDs []abi.DealID }
Failure conditions
  • Caller is not a Signable actor
  • len(params.Deals) == 0
  • Deal's Provider address:
    • Is not able to be resolved
    • Does not have a Miner actor CodeCID
      • NOTE: This is not currently enforced
  • Caller is not the Worker address associated with deal's Provider
  • VerifiedRegistry.UseBytes fails for any deal.Client and deal.PieceSize
  • For any ClientDealProposal:
    • validateDeal fails:
      • Signature verification fails
      • len(proposal.Label) > DealMaxLabelSize (256)
      • proposal.PieceSize.Validate() fails
      • proposal.PieceCID.Defined() == false
      • proposal.PieceCID.Prefix() != PieceCIDPrefix
      • proposal.EndEpoch <= proposal.StartEpoch
      • rt.CurrEpoch() > proposal.StartEpoch
      • Invalid proposal duration
      • Invalid proposal price
      • Invalid proposal.ProviderCollateral
      • Invalid proposal.ClientCollateral
    • deal.Provider is different than any other deal's Provider
    • deal.Client address is not able to be resolved
    • Specified collateral is not available in Client or Provider's escrow balance
    • Proposal already exists in st.PendingProposals

5. VerifyDealsForActivation

func (A Actor) VerifyDealsForActivation(rt Runtime, params *VerifyDealsForActivationParams) *VerifyDealsForActivationReturn

Allows a Miner to verify that a set of Storage Deals is valid for a Sector. This method is called by a Miner after publishing a set of Storage Deals via PublishStorageDeals. After accepting Client data from published deals, the Miner PreCommits a Sector containing those deals via Miner.PreCommitSector.

  • As part of this, the Miner declares an expiration epoch for the containing Sector. The start epoch of the Sector is defined as rt.CurrEpoch() (when Miner.PreCommitSector is invoked).
  • Miner.PreCommitSector must record the total DealWeight of the Sector. In order to calculate this, VerifyDealsForActivation is invoked.

This method iterates over a slice of provided DealIDs, validates that the deal in question is unique and may be activated, then calculates each deal's respective weight. The sum of these weights is returned, split into weight corresponding to verified deals and weight corresponding to regular deals.

Parameters
type VerifyDealsForActivationParams struct { DealIDs []abi.DealID SectorExpiry abi.ChainEpoch SectorStart abi.ChainEpoch }

DealIDs: A slice of DealIDs included in a Sector the Miner is PreCommitting.

  • Requirements:
    • Each DealID must correspond to an entry in st.Proposals
    • Caller must be the Provider of each referenced deal
    • Each DealID must be unique within the slice

SectorExpiry: The expiration epoch for the containing Sector declared by the Miner.

  • Requirements:
    • Each deal's EndEpoch must be less than or equal to SectorExpiry

SectorStart: The start epoch for the containing Sector. Set to rt.CurrEpoch().

  • Requirements:
    • Each deal's StartEpoch must come after SectorStart
Return
type VerifyDealsForActivationReturn struct { DealWeight abi.DealWeight VerifiedDealWeight abi.DealWeight DealSpace uint64 }

DealWeight: The sum of DealWeights calculated for each passed-in DealID not corresponding to a Verified Deal.

  • Notes:
    • Measured in byte-epochs
    • Calculated as proposal.Duration() * proposal.PieceSize
  • Invariants:
    • DealWeight >= 0

VerifiedDealWeight: The sum of DealWeights calculated for each passed-in DealID corresponding to a Verified Deal.

  • Notes:
    • Measured in byte-epochs
    • Calculated as proposal.Duration() * proposal.PieceSize
  • Invariants:
    • VerifiedDealWeight >= 0

DealSpace: The sum of the PieceSize of each passed-in deal.

  • Notes:
    • Measured in bytes
  • Invariants:
    • DealSpace >= 0
Failure conditions
  • Caller is not a Miner actor
  • params.DealIDs contains a duplicate entry
  • A referenced DealID is not found in st.Proposals
  • A referenced deal cannot be activated (validateDealCanActivate):
    • Caller is not the deal's Provider
    • Deal's StartEpoch has already elapsed (CurrEpoch > proposal.StartEpoch)
    • Deal's EndEpoch exceeds the Sector's expiration (proposal.EndEpoch > params.SectorExpiry)

6. ActivateDeals

func (a Actor) ActivateDeals(rt Runtime, params *ActivateDealsParams) *adt.EmptyValue

After PreCommitting a Sector with its contained deals, a Miner attempts to ProveCommit the Sector via Miner.ProveCommitSector, which submits a proof of replication to the StoragePower actor (SubmitPoRepForBulkVerify). If this proof is valid, the StoragePower actor calls Miner.ConfirmSectorProofsValid at the end of the same epoch.

For each valid Sector proof, Miner.ConfirmSectorProofsValid activates the deals stored in that Sector via ActivateDeals. This method:

  • Validates that each DealProposal may be activated
  • Checks that each DealProposal:
    • Exists in st.Proposals
    • Exists in st.PendingProposals
    • Does not exist in st.States
      • This would imply that the deal has already been included in a Sector
  • Inserts each DealProposal into st.States by its DealID.

Each deal now has a corresponding DealState. The DealState.SectorStartEpoch of each deal is set to rt.CurrEpoch(), while the other two fields are set to epochUndefined (ChainEpoch(-1)).

Parameters
type ActivateDealsParams struct { DealIDs []abi.DealID SectorExpiry abi.ChainEpoch }

DealIDs: A slice of DealIDs being activated.

  • Notes:
    • Each DealID is stored within the same Sector.
  • Requirements:
    • Each DealID must correspond to an entry in st.Proposals
    • Caller must be the Provider of each referenced deal
    • No deal's StartEpoch should have already elapsed
    • No deal should exist within st.States
    • Each deal must exist within st.PendingProposals

SectorExpiry: The expiration epoch of the containing Sector.

  • Requirements:
    • Each deal's EndEpoch must be less than or equal to SectorExpiry
Failure conditions
  • Caller is not a Miner actor
  • A referenced deal cannot be activated (validateDealCanActivate):
    • Caller is not the deal's Provider
    • Deal's StartEpoch has already elapsed (CurrEpoch > proposal.StartEpoch)
    • Deal's EndEpoch exceeds the Sector's expiration (proposal.EndEpoch > params.SectorExpiry)
  • A referenced deal exists within st.States
  • A referenced deal was not found within st.Proposals
  • A referenced deal was not found within st.PendingProposals

7. OnMinerSectorsTerminate

func (a Actor) OnMinerSectorsTerminate(rt Runtime, params *OnMinerSectorsTerminateParams) *adt.EmptyValue

A Miner may invoke this method from one of the following exported methods:

  • Miner.TerminateSectors
  • Miner.ReportConsensusFault
  • Miner.OnDeferredCronEvent

This method simply iterates over the provided DealIDs and sets the deal's DealState.SlashEpoch to params.Epoch. The actual slashing occurs the next time this deal is processed in CronTick.

  • Special cases:
    • If the deal has already expired (deal.EndEpoch <= params.Epoch), SlashEpoch is not set.
    • If the deal's SlashEpoch has already been set (DealState.SlashEpoch != epochUndefined), nothing happens.
Parameters
type OnMinerSectorsTerminateParams struct { Epoch abi.ChainEpoch DealIDs []abi.DealID }

Epoch: The epoch in which the containing Sector was terminated.

DealIDs: A slice of DealIDs whose containing sector has been terminated.

Failure conditions
  • Caller is not a Miner actor
  • For any given passed-in DealID, Caller is not that deal's Provider (panic)
  • Deal does not exist in st.States
    • Note: This implies the deal was never activated and so should not be included in a Sector

8. ComputeDataCommitment

func (a Actor) ComputeDataCommitment(rt Runtime, params *ComputeDataCommitmentParams) *cbg.CborCid

After PreCommitting a Sector with its contained deals, a Miner attempts to ProveCommit the Sector via Miner.ProveCommitSector, which submits a proof of replication to the StoragePower actor (SubmitPoRepForBulkVerify).

As part of this process, the Miner invokes ComputeDataCommitment to calculate the Unsealed Sector CID for the Sector in question. The resulting UnsealedCID (also referred to as "CommD," or "data commitment") is passed in along with other values to SubmitPoRepForBulkVerify.

This method returns the result of the syscall rt.Syscalls().ComputeUnsealedSectorCID.

Parameters
type ComputeDataCommitmentParams struct { DealIDs []abi.DealID SectorType abi.RegisteredSealProof }

DealIDs: A slice of DealIDs that will all be stored in the same Sector.

  • Requirements:
    • Each deal must exist within st.Proposals

SectorType: The seal proof type used by the Miner for this Sector. Should be equivalent to the Miner's MinerInfo.SealProofType.

Failure conditions
  • Caller is not a Miner actor
  • A referenced DealID is not found within st.Proposals

9. CronTick

func (a Actor) CronTick(rt Runtime, _ *adt.EmptyValue) *adt.EmptyValue

CronTick is a special method invoked by the Cron actor at the end of each tipset.

The primary purpose of CronTick is to handle updates to deals that need to occur at specific epochs. These updates are tracked by st.DealOpsByEpoch, which maps an epoch to a list of deals that need updates at that epoch. CronTick iterates over each deal for a given epoch.

Typically, CronTick is only concerned with a single epoch, rt.CurrEpoch(). However, in the rare case that an epoch does not invoke the Cron actor, CronTick iterates over each of the deals for each epoch since it was last invoked (st.LastCron + 1 to rt.CurrEpoch()).

Deals are initially inserted in st.DealOpsByEpoch in PublishStorageDeals, where they are inserted by DealProposal.StartEpoch. This means that during the deal's start epoch, CronTick will process an update for that deal for the first time.

The operations performed on each deal depend in large part on the deal's respective DealState entry (in st.States), which is created on a deal's activation via ActivateDeals:

  • SectorStartEpoch: Set to rt.CurrEpoch(). Never updated.
  • LastUpdatedEpoch: Set to epochUndefined (-1). Updated each time the deal is processed in CronTick.
  • SlashEpoch: Set to epochUndefined (-1). Updated if a deal's containing Sector is terminated via OnMinerSectorsTerminate.

The first time CronTick processes a specific deal, it will perform ONE of the following actions:

  • Init Timeout: If a deal was published via PublishStorageDeals but never activated via ActivateDeals, CronTick considers it "timed out," as its StartEpoch has elapsed without it appearing in a proven Sector.
    • Identification:
      • DealID exists in st.Proposals and st.PendingProposals, but NOT within st.States
    • Actions taken:
      • deal.ClientCollateral and deal.ClientStorageFee are unlocked and returned to deal.Client
      • A penalty is calculated and slashed from deal.ProviderCollateral. Any amount remaining is unlocked and returned to deal.Provider.
      • If the deal in question is a Verified Deal (see VerifiedRegistry actor), the bytes consumed by the deal are restored.
      • The deal is deleted from st.Proposals
    • Notes:
      • If a deal is in Init Timeout, no other updates will be performed.
  • Init Update: If this is the first time a deal has been processed by CronTick, CronTick performs an Init Update.
    • Identification:
      • DealID exists in st.Proposals, st.PendingProposals, and st.States
      • DealState.LastUpdatedEpoch == epochUndefined
    • Actions taken:
      • Deal's entry is deleted from st.PendingProposals

CronTick performs these updates for ALL deals, EXCEPT those caught by Init Timeout:

  • Client-Provider Payment: Clients pay Providers for storage incrementally. Payment is due each time a deal is processed by CronTick, and is calculated from deal.StoragePricePerEpoch times the number of epochs elapsed since the last time the deal was processed. If the deal has already ended (rt.CurrEpoch > deal.EndEpoch), payment is not due for epochs between EndEpoch and CurrEpoch.
    • Special case: If deal.Provider was slashed for this deal, deal.Client will only be charged up to state.SlashEpoch at max.
    • Actions taken:
      • Payment amount is transferred from the Client's LockedTable to the Provider's EscrowTable.
  • Handle Slashed Deal: If a deal's containing Sector was terminated, the Provider must be slashed.
    • Identification:
      • DealState.SlashEpoch != epochUndefined
    • Actions taken:
      • deal.ClientCollateral is unlocked and returned to deal.Client
      • Any remaining storage fees are unlocked and returned to deal.Client
      • deal.ProviderCollateral is slashed by removing the amount from the Provider's EscrowTable balance and sending it to the BurntFunds actor
      • The deal is deleted from st.Proposals and st.States
  • Handle Expired Deal: If a deal's EndEpoch has elapsed (and the deal was not slashed), the deal has been completed.
    • Identification:
      • rt.CurrEpoch() >= deal.EndEpoch, AND DealState.SlashEpoch == epochUndefined
    • Actions taken:
      • deal.ClientCollateral and deal.ProviderCollateral are unlocked and returned to deal.Client and deal.Provider
      • The deal is deleted from st.Proposals and st.States
  • Handle Ongoing Deal: A deal that has not been slashed and has not expired must be processed again in a future epoch.
    • Identification:
      • rt.CurrEpoch() < deal.EndEpoch, AND DealState.SlashEpoch == epochUndefined
    • Actions taken:
      • DealState.LastUpdatedEpoch is set to rt.CurrEpoch()
      • The deal is re-inserted in st.DealOpsByState at the epoch rt.CurrEpoch() + DealUpdatesInterval (100)
        • NOTE: If this epoch is beyond the deal's EndEpoch, the EndEpoch is used instead.

No matter the case, once an epoch's deals have been iterated over and processed, that epoch's entries are deleted from st.DealOpsByEpoch.

Failure conditions
  • Caller is not the Cron actor
  • For any deal/DealID referenced by StartEpoch in st.DealOpsByEpoch:
    • Deal does not exist in st.Proposals
    • DealState does not exist in st.States, AND:
      • deal.StartEpoch > rt.CurrEpoch() (panic)
      • processDealInitTimeout fails to unlock or slash balances
    • DealState.LastUpdatedEpoch == epochUndefined AND deal does not exist in st.PendingProposals
    • updatePendingDealState fails:
      • DealState.LastUpdatedEpoch > rt.CurrEpoch() (panic)
      • DealState.SlashEpoch > rt.CurrEpoch() (panic)
      • DealState.SlashEpoch > deal.EndEpoch (panic)
      • Payment from deal.Client to deal.Provider fails (st.transferBalance)
      • If DealState.SlashEpoch is set, AND:
        • Client collateral and remaining storage fee fail to be unlocked
        • Provider collateral fails to be slashed
      • If rt.CurrEpoch() >= deal.EndEpoch, AND:
        • DealState.SectorStartEpoch == epochUndefined (panic)
        • Provider and Client collateral fail to be unlocked
Select a repo