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:
Actor Type:
Exported Methods:
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.
array[DealID]DealProposal
DealProposal
States
: Tracks important epochs for active deals that have been included in a Sector.
array[DealID]DealState
DealState
PendingProposals
: Tracks proposals that have not reached their start epoch. Prevents Miners from publishing the same DealProposal
twice.
map[DealCid]DealProposal
DealProposal
Cid to its DealProposal
EscrowTable
: Tracks funds held in escrow. Includes locked funds (see below).
map[Address]TokenAmount
LockedTable
: Tracks funds held in escrow that are also locked.
map[Address]TokenAmount
NextID
: Tracks the next DealID that will be assigned to a published storage deal
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.
map[ChainEpoch]Set
DealOpsByEpoch
in PublishStorageDeals
.LastCron
: The last epoch in which CronTick
was called. Subsequent CronTick
s perform updates from LastCron + 1
to rt.CurrEpoch
.
LastCron >= -1
TotalClientLockedCollateral
: Sum of all ClientCollateral locked in published deals. Unlocked when corresponding deals are terminated.
TotalClientLockedCollateral >= 0
TotalProviderLockedCollateral
: Sum of all ProviderCollateral locked in published deals. Unlocked when corresponding deals are terminated.
TotalProviderLockedCollateral >= 0
TotalClientStorageFee
: Sum of all storage fees locked in published deals. Unlocked over time as payments are made.
TotalClientStorageFee >= 0
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.
PieceCID.Defined() == true
PieceSize
: The number of bytes that will be stored, padded to a power of two with extra zeroes.
PieceSize.Validate()
(abi/piece.go
)VerifiedDeal
: Whether the deal is a Verified Deal (see VerifiedRegistry spec)
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.
Provider
: The Miner storing the data for Client
.
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.
EndEpoch > StartEpoch
StoragePricePerEpoch
: The token amount paid by Client
for each epoch the deal is active.
StartEpoch
to EndEpoch
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
.
OnMinerSectorsTerminate
for details on refund/slashingProviderCollateral >= 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
OnMinerSectorsTerminate
for details on refund/slashingClientCollateral >= 0
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.
SectorStartEpoch >= 0
LastUpdatedEpoch
: Tracks the last epoch in which a deal's DealState
was updated.
LastUpdatedEpoch >= -1
SlashEpoch
: Tracks the epoch in which a deal was slashed.
SlashEpoch >= -1
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)
.
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
ValueReceived()
must be greater than zeroAdditionally, an empty entry is created in the LockedTable
if one does not exist.
ValueReceived()
providerOrClientAddress
fails to resolveproviderOrClientAddress
does not have an associated CodeCIDproviderOrClientAddress
resolves to a Miner actor, and Caller is not one of the Miner's control addressesproviderOrClientAddress
does not resolve to a Miner actor, and Caller is not a Signable-type actorfunc (a Actor) WithdrawBalance(rt Runtime, params *WithdrawBalanceParams) *adt.EmptyValue
Allows an actor to withdraw available balance held in escrow.
type WithdrawBalanceParams struct {
ProviderOrClientAddress addr.Address
Amount abi.TokenAmount
}
ProviderOrClientAddress
: Must resolve to a Signable-type actor, or a Miner actor.
ProviderOrClientAddress
resolves to a Miner actor, Caller must be one of the Miner's control addressesAmount
: The amount to withdraw from escrow.
Amount
is available, this method will send the entire available balance.Amount >= 0
params.Amount < 0
providerOrClientAddress
does not have an associated CodeCIDproviderOrClientAddress
resolves to a Miner actor, and Caller is not one of the Miner's control addressesproviderOrClientAddress
is not a Miner actor, and is not Callerfunc (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.
State
:
st.PendingProposals
: Maps the Cid of the deal's Proposal
to the Proposal
itselfst.Proposals
: Maps the unique DealID to the deal's Proposal
st.DealOpsByEpoch
: Maps the Proposal's StartEpoch
to the unique DealID
CronTick
st.EscrowTable
entries.ClientCollateral
, as well as the total storage fee calculated from various deal parameters.PublishStorageDealsParams
contains a slice of signed DealProposals
.
type PublishStorageDealsParams struct {
Deals []ClientDealProposal
}
Deals
: See ClientDealProposal
below
len(Deals) != 0
type ClientDealProposal struct {
Proposal DealProposal
ClientSignature acrypto.Signature
}
Proposal
: The DealProposal agreed to between the Miner and Client
nil
ClientSignature
: A signature over Proposal
by Proposal.Client
nil
rt.Syscalls().VerifySignature
A slice of DealIDs corresponding to the newly-published Storage Deals.
type PublishStorageDealsReturn struct {
IDs []abi.DealID
}
len(params.Deals) == 0
deal.Client
and deal.PieceSize
ClientDealProposal
:
validateDeal
fails:
len(proposal.Label) > DealMaxLabelSize
(256)proposal.PieceSize.Validate()
failsproposal.PieceCID.Defined() == false
proposal.PieceCID.Prefix() != PieceCIDPrefix
proposal.EndEpoch <= proposal.StartEpoch
rt.CurrEpoch() > proposal.StartEpoch
proposal.ProviderCollateral
proposal.ClientCollateral
deal.Provider
is different than any other deal's Providerdeal.Client
address is not able to be resolvedst.PendingProposals
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
.
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.
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.
DealID
must correspond to an entry in st.Proposals
Provider
of each referenced dealDealID
must be unique within the sliceSectorExpiry
: The expiration epoch for the containing Sector declared by the Miner.
EndEpoch
must be less than or equal to SectorExpiry
SectorStart
: The start epoch for the containing Sector. Set to rt.CurrEpoch()
.
StartEpoch
must come after SectorStart
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.
proposal.Duration() * proposal.PieceSize
DealWeight >= 0
VerifiedDealWeight
: The sum of DealWeights calculated for each passed-in DealID
corresponding to a Verified Deal.
proposal.Duration() * proposal.PieceSize
VerifiedDealWeight >= 0
DealSpace
: The sum of the PieceSize
of each passed-in deal.
DealSpace >= 0
params.DealIDs
contains a duplicate entryDealID
is not found in st.Proposals
validateDealCanActivate
):
Provider
StartEpoch
has already elapsed (CurrEpoch > proposal.StartEpoch
)EndEpoch
exceeds the Sector's expiration (proposal.EndEpoch > params.SectorExpiry
)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:
st.Proposals
st.PendingProposals
st.States
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)
).
type ActivateDealsParams struct {
DealIDs []abi.DealID
SectorExpiry abi.ChainEpoch
}
DealIDs
: A slice of DealIDs
being activated.
DealID
is stored within the same Sector.DealID
must correspond to an entry in st.Proposals
Provider
of each referenced dealStartEpoch
should have already elapsedst.States
st.PendingProposals
SectorExpiry
: The expiration epoch of the containing Sector.
EndEpoch
must be less than or equal to SectorExpiry
validateDealCanActivate
):
Provider
StartEpoch
has already elapsed (CurrEpoch > proposal.StartEpoch
)EndEpoch
exceeds the Sector's expiration (proposal.EndEpoch > params.SectorExpiry
)st.States
st.Proposals
st.PendingProposals
func (a Actor) OnMinerSectorsTerminate(rt Runtime, params *OnMinerSectorsTerminateParams) *adt.EmptyValue
A Miner may invoke this method from one of the following exported methods:
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
.
deal.EndEpoch <= params.Epoch
), SlashEpoch
is not set.SlashEpoch
has already been set (DealState.SlashEpoch != epochUndefined
), nothing happens.
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.
DealID
, Caller is not that deal's Provider (panic)st.States
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
.
type ComputeDataCommitmentParams struct {
DealIDs []abi.DealID
SectorType abi.RegisteredSealProof
}
DealIDs
: A slice of DealIDs
that will all be stored in the same Sector.
st.Proposals
SectorType
: The seal proof type used by the Miner for this Sector. Should be equivalent to the Miner's MinerInfo.SealProofType
.
DealID
is not found within st.Proposals
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:
PublishStorageDeals
but never activated via ActivateDeals
, CronTick
considers it "timed out," as its StartEpoch
has elapsed without it appearing in a proven Sector.
DealID
exists in st.Proposals
and st.PendingProposals
, but NOT within st.States
deal.ClientCollateral
and deal.ClientStorageFee
are unlocked and returned to deal.Client
deal.ProviderCollateral
. Any amount remaining is unlocked and returned to deal.Provider
.st.Proposals
CronTick
, CronTick
performs an Init Update.
DealID
exists in st.Proposals
, st.PendingProposals
, and st.States
DealState.LastUpdatedEpoch == epochUndefined
st.PendingProposals
CronTick
performs these updates for ALL deals, EXCEPT those caught by Init Timeout:
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
.
deal.Provider
was slashed for this deal, deal.Client
will only be charged up to state.SlashEpoch
at max.LockedTable
to the Provider's EscrowTable
.DealState.SlashEpoch != epochUndefined
deal.ClientCollateral
is unlocked and returned to deal.Client
deal.Client
deal.ProviderCollateral
is slashed by removing the amount from the Provider's EscrowTable
balance and sending it to the BurntFunds actorst.Proposals
and st.States
EndEpoch
has elapsed (and the deal was not slashed), the deal has been completed.
rt.CurrEpoch() >= deal.EndEpoch
, AND DealState.SlashEpoch == epochUndefined
deal.ClientCollateral
and deal.ProviderCollateral
are unlocked and returned to deal.Client
and deal.Provider
st.Proposals
and st.States
rt.CurrEpoch() < deal.EndEpoch
, AND DealState.SlashEpoch == epochUndefined
DealState.LastUpdatedEpoch
is set to rt.CurrEpoch()
st.DealOpsByState
at the epoch rt.CurrEpoch() + DealUpdatesInterval
(100)
No matter the case, once an epoch's deals have been iterated over and processed, that epoch's entries are deleted from st.DealOpsByEpoch
.
DealID
referenced by StartEpoch
in st.DealOpsByEpoch
:
st.Proposals
DealState
does not exist in st.States
, AND:
deal.StartEpoch > rt.CurrEpoch()
(panic)processDealInitTimeout
fails to unlock or slash balancesDealState.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)deal.Client
to deal.Provider
fails (st.transferBalance
)DealState.SlashEpoch
is set, AND:
rt.CurrEpoch() >= deal.EndEpoch
, AND:
DealState.SectorStartEpoch == epochUndefined
(panic)