github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/market/market_state.go (about) 1 package market 2 3 import ( 4 "bytes" 5 6 "github.com/filecoin-project/go-state-types/abi" 7 "github.com/filecoin-project/go-state-types/big" 8 "github.com/filecoin-project/go-state-types/exitcode" 9 "github.com/ipfs/go-cid" 10 xerrors "golang.org/x/xerrors" 11 12 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 13 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 14 ) 15 16 const epochUndefined = abi.ChainEpoch(-1) 17 18 // Market mutations 19 // add / rm balance 20 // pub deal (always provider) 21 // activate deal (miner) 22 // end deal (miner terminate, expire(no activation)) 23 24 // BalanceLockingReason is the reason behind locking an amount. 25 type BalanceLockingReason int 26 27 const ( 28 ClientCollateral BalanceLockingReason = iota 29 ClientStorageFee 30 ProviderCollateral 31 ) 32 33 // Bitwidth of AMTs determined empirically from mutation patterns and projections of mainnet data. 34 const ProposalsAmtBitwidth = 5 35 const StatesAmtBitwidth = 6 36 37 type State struct { 38 Proposals cid.Cid // AMT[DealID]DealProposal 39 States cid.Cid // AMT[DealID]DealState 40 41 // PendingProposals tracks dealProposals that have not yet reached their deal start date. 42 // We track them here to ensure that miners can't publish the same deal proposal twice 43 PendingProposals cid.Cid // Set[DealCid] 44 45 // Total amount held in escrow, indexed by actor address (including both locked and unlocked amounts). 46 EscrowTable cid.Cid // BalanceTable 47 48 // Amount locked, indexed by actor address. 49 // Note: the amounts in this table do not affect the overall amount in escrow: 50 // only the _portion_ of the total escrow amount that is locked. 51 LockedTable cid.Cid // BalanceTable 52 53 NextID abi.DealID 54 55 // Metadata cached for efficient iteration over deals. 56 DealOpsByEpoch cid.Cid // SetMultimap, HAMT[epoch]Set 57 LastCron abi.ChainEpoch 58 59 // Total Client Collateral that is locked -> unlocked when deal is terminated 60 TotalClientLockedCollateral abi.TokenAmount 61 // Total Provider Collateral that is locked -> unlocked when deal is terminated 62 TotalProviderLockedCollateral abi.TokenAmount 63 // Total storage fee that is locked in escrow -> unlocked when payments are made 64 TotalClientStorageFee abi.TokenAmount 65 } 66 67 func ConstructState(store adt.Store) (*State, error) { 68 emptyProposalsArrayCid, err := adt.StoreEmptyArray(store, ProposalsAmtBitwidth) 69 if err != nil { 70 return nil, xerrors.Errorf("failed to create empty array: %w", err) 71 } 72 emptyStatesArrayCid, err := adt.StoreEmptyArray(store, StatesAmtBitwidth) 73 if err != nil { 74 return nil, xerrors.Errorf("failed to create empty states array: %w", err) 75 } 76 77 emptyPendingProposalsMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) 78 if err != nil { 79 return nil, xerrors.Errorf("failed to create empty map: %w", err) 80 } 81 emptyDealOpsHamtCid, err := StoreEmptySetMultimap(store, builtin.DefaultHamtBitwidth) 82 if err != nil { 83 return nil, xerrors.Errorf("failed to create empty multiset: %w", err) 84 } 85 emptyBalanceTableCid, err := adt.StoreEmptyMap(store, adt.BalanceTableBitwidth) 86 if err != nil { 87 return nil, xerrors.Errorf("failed to create empty balance table: %w", err) 88 } 89 90 return &State{ 91 Proposals: emptyProposalsArrayCid, 92 States: emptyStatesArrayCid, 93 PendingProposals: emptyPendingProposalsMapCid, 94 EscrowTable: emptyBalanceTableCid, 95 LockedTable: emptyBalanceTableCid, 96 NextID: abi.DealID(0), 97 DealOpsByEpoch: emptyDealOpsHamtCid, 98 LastCron: abi.ChainEpoch(-1), 99 100 TotalClientLockedCollateral: abi.NewTokenAmount(0), 101 TotalProviderLockedCollateral: abi.NewTokenAmount(0), 102 TotalClientStorageFee: abi.NewTokenAmount(0), 103 }, nil 104 } 105 106 //////////////////////////////////////////////////////////////////////////////// 107 // Deal state operations 108 //////////////////////////////////////////////////////////////////////////////// 109 110 func (m *marketStateMutation) updatePendingDealState(rt Runtime, state *DealState, deal *DealProposal, epoch abi.ChainEpoch) (amountSlashed abi.TokenAmount, nextEpoch abi.ChainEpoch, removeDeal bool) { 111 amountSlashed = abi.NewTokenAmount(0) 112 113 everUpdated := state.LastUpdatedEpoch != epochUndefined 114 everSlashed := state.SlashEpoch != epochUndefined 115 116 builtin.RequireState(rt, !everUpdated || (state.LastUpdatedEpoch <= epoch), "deal updated at future epoch %d", state.LastUpdatedEpoch) 117 118 // This would be the case that the first callback somehow triggers before it is scheduled to 119 // This is expected not to be able to happen 120 if deal.StartEpoch > epoch { 121 return amountSlashed, epochUndefined, false 122 } 123 124 paymentEndEpoch := deal.EndEpoch 125 if everSlashed { 126 builtin.RequireState(rt, epoch >= state.SlashEpoch, "current epoch less than deal slash epoch %d", state.SlashEpoch) 127 builtin.RequireState(rt, state.SlashEpoch <= deal.EndEpoch, "deal slash epoch %d after deal end %d", state.SlashEpoch, deal.EndEpoch) 128 paymentEndEpoch = state.SlashEpoch 129 } else if epoch < paymentEndEpoch { 130 paymentEndEpoch = epoch 131 } 132 133 paymentStartEpoch := deal.StartEpoch 134 if everUpdated && state.LastUpdatedEpoch > paymentStartEpoch { 135 paymentStartEpoch = state.LastUpdatedEpoch 136 } 137 138 numEpochsElapsed := paymentEndEpoch - paymentStartEpoch 139 140 { 141 // Process deal payment for the elapsed epochs. 142 totalPayment := big.Mul(big.NewInt(int64(numEpochsElapsed)), deal.StoragePricePerEpoch) 143 144 // the transfer amount can be less than or equal to zero if a deal is slashed before or at the deal's start epoch. 145 if totalPayment.GreaterThan(big.Zero()) { 146 err := m.transferBalance(deal.Client, deal.Provider, totalPayment) 147 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to transfer %v from %v to %v", 148 totalPayment, deal.Client, deal.Provider) 149 } 150 } 151 152 if everSlashed { 153 // unlock client collateral and locked storage fee 154 paymentRemaining, err := dealGetPaymentRemaining(deal, state.SlashEpoch) 155 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to compute remaining payment") 156 157 // unlock remaining storage fee 158 err = m.unlockBalance(deal.Client, paymentRemaining, ClientStorageFee) 159 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to unlock remaining client storage fee") 160 161 // unlock client collateral 162 err = m.unlockBalance(deal.Client, deal.ClientCollateral, ClientCollateral) 163 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to unlock client collateral") 164 165 // slash provider collateral 166 amountSlashed = deal.ProviderCollateral 167 err = m.slashBalance(deal.Provider, amountSlashed, ProviderCollateral) 168 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "slashing balance") 169 return amountSlashed, epochUndefined, true 170 } 171 172 if epoch >= deal.EndEpoch { 173 m.processDealExpired(rt, deal, state) 174 return amountSlashed, epochUndefined, true 175 } 176 177 // We're explicitly not inspecting the end epoch and may process a deal's expiration late, in order to prevent an outsider 178 // from loading a cron tick by activating too many deals with the same end epoch. 179 nextEpoch = epoch + DealUpdatesInterval 180 181 return amountSlashed, nextEpoch, false 182 } 183 184 // Deal start deadline elapsed without appearing in a proven sector. 185 // Slash a portion of provider's collateral, and unlock remaining collaterals 186 // for both provider and client. 187 func (m *marketStateMutation) processDealInitTimedOut(rt Runtime, deal *DealProposal) abi.TokenAmount { 188 if err := m.unlockBalance(deal.Client, deal.TotalStorageFee(), ClientStorageFee); err != nil { 189 rt.Abortf(exitcode.ErrIllegalState, "failure unlocking client storage fee: %s", err) 190 } 191 if err := m.unlockBalance(deal.Client, deal.ClientCollateral, ClientCollateral); err != nil { 192 rt.Abortf(exitcode.ErrIllegalState, "failure unlocking client collateral: %s", err) 193 } 194 195 amountSlashed := CollateralPenaltyForDealActivationMissed(deal.ProviderCollateral) 196 amountRemaining := big.Sub(deal.ProviderBalanceRequirement(), amountSlashed) 197 198 if err := m.slashBalance(deal.Provider, amountSlashed, ProviderCollateral); err != nil { 199 rt.Abortf(exitcode.ErrIllegalState, "failed to slash balance: %s", err) 200 } 201 202 if err := m.unlockBalance(deal.Provider, amountRemaining, ProviderCollateral); err != nil { 203 rt.Abortf(exitcode.ErrIllegalState, "failed to unlock deal provider balance: %s", err) 204 } 205 206 return amountSlashed 207 } 208 209 // Normal expiration. Unlock collaterals for both provider and client. 210 func (m *marketStateMutation) processDealExpired(rt Runtime, deal *DealProposal, state *DealState) { 211 builtin.RequireState(rt, state.SectorStartEpoch != epochUndefined, "sector start epoch undefined") 212 213 // Note: payment has already been completed at this point (_rtProcessDealPaymentEpochsElapsed) 214 err := m.unlockBalance(deal.Provider, deal.ProviderCollateral, ProviderCollateral) 215 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed unlocking deal provider balance") 216 217 err = m.unlockBalance(deal.Client, deal.ClientCollateral, ClientCollateral) 218 builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed unlocking deal client balance") 219 } 220 221 func (m *marketStateMutation) generateStorageDealID() abi.DealID { 222 ret := m.nextDealId 223 m.nextDealId = m.nextDealId + abi.DealID(1) 224 return ret 225 } 226 227 //////////////////////////////////////////////////////////////////////////////// 228 // State utility functions 229 //////////////////////////////////////////////////////////////////////////////// 230 231 func dealProposalIsInternallyValid(rt Runtime, proposal ClientDealProposal) error { 232 // Note: we do not verify the provider signature here, since this is implicit in the 233 // authenticity of the on-chain message publishing the deal. 234 buf := bytes.Buffer{} 235 err := proposal.Proposal.MarshalCBOR(&buf) 236 if err != nil { 237 return xerrors.Errorf("proposal signature verification failed to marshal proposal: %w", err) 238 } 239 err = rt.VerifySignature(proposal.ClientSignature, proposal.Proposal.Client, buf.Bytes()) 240 if err != nil { 241 return xerrors.Errorf("signature proposal invalid: %w", err) 242 } 243 return nil 244 } 245 246 func dealGetPaymentRemaining(deal *DealProposal, slashEpoch abi.ChainEpoch) (abi.TokenAmount, error) { 247 if slashEpoch > deal.EndEpoch { 248 return big.Zero(), xerrors.Errorf("deal slash epoch %d after end epoch %d", slashEpoch, deal.EndEpoch) 249 } 250 251 // Payments are always for start -> end epoch irrespective of when the deal is slashed. 252 if slashEpoch < deal.StartEpoch { 253 slashEpoch = deal.StartEpoch 254 } 255 256 durationRemaining := deal.EndEpoch - slashEpoch 257 if durationRemaining < 0 { 258 return big.Zero(), xerrors.Errorf("deal remaining duration negative: %d", durationRemaining) 259 } 260 261 return big.Mul(big.NewInt(int64(durationRemaining)), deal.StoragePricePerEpoch), nil 262 } 263 264 // MarketStateMutationPermission is the mutation permission on a state field 265 type MarketStateMutationPermission int 266 267 const ( 268 // Invalid means NO permission 269 Invalid MarketStateMutationPermission = iota 270 // ReadOnlyPermission allows reading but not mutating the field 271 ReadOnlyPermission 272 // WritePermission allows mutating the field 273 WritePermission 274 ) 275 276 type marketStateMutation struct { 277 st *State 278 store adt.Store 279 280 proposalPermit MarketStateMutationPermission 281 dealProposals *DealArray 282 283 statePermit MarketStateMutationPermission 284 dealStates *DealMetaArray 285 286 escrowPermit MarketStateMutationPermission 287 escrowTable *adt.BalanceTable 288 289 pendingPermit MarketStateMutationPermission 290 pendingDeals *adt.Set 291 292 dpePermit MarketStateMutationPermission 293 dealsByEpoch *SetMultimap 294 295 lockedPermit MarketStateMutationPermission 296 lockedTable *adt.BalanceTable 297 totalClientLockedCollateral abi.TokenAmount 298 totalProviderLockedCollateral abi.TokenAmount 299 totalClientStorageFee abi.TokenAmount 300 301 nextDealId abi.DealID 302 } 303 304 func (s *State) mutator(store adt.Store) *marketStateMutation { 305 return &marketStateMutation{st: s, store: store} 306 } 307 308 func (m *marketStateMutation) build() (*marketStateMutation, error) { 309 if m.proposalPermit != Invalid { 310 proposals, err := AsDealProposalArray(m.store, m.st.Proposals) 311 if err != nil { 312 return nil, xerrors.Errorf("failed to load deal proposals: %w", err) 313 } 314 m.dealProposals = proposals 315 } 316 317 if m.statePermit != Invalid { 318 states, err := AsDealStateArray(m.store, m.st.States) 319 if err != nil { 320 return nil, xerrors.Errorf("failed to load deal state: %w", err) 321 } 322 m.dealStates = states 323 } 324 325 if m.lockedPermit != Invalid { 326 lt, err := adt.AsBalanceTable(m.store, m.st.LockedTable) 327 if err != nil { 328 return nil, xerrors.Errorf("failed to load locked table: %w", err) 329 } 330 m.lockedTable = lt 331 m.totalClientLockedCollateral = m.st.TotalClientLockedCollateral.Copy() 332 m.totalClientStorageFee = m.st.TotalClientStorageFee.Copy() 333 m.totalProviderLockedCollateral = m.st.TotalProviderLockedCollateral.Copy() 334 } 335 336 if m.escrowPermit != Invalid { 337 et, err := adt.AsBalanceTable(m.store, m.st.EscrowTable) 338 if err != nil { 339 return nil, xerrors.Errorf("failed to load escrow table: %w", err) 340 } 341 m.escrowTable = et 342 } 343 344 if m.pendingPermit != Invalid { 345 pending, err := adt.AsSet(m.store, m.st.PendingProposals, builtin.DefaultHamtBitwidth) 346 if err != nil { 347 return nil, xerrors.Errorf("failed to load pending proposals: %w", err) 348 } 349 m.pendingDeals = pending 350 } 351 352 if m.dpePermit != Invalid { 353 dbe, err := AsSetMultimap(m.store, m.st.DealOpsByEpoch, builtin.DefaultHamtBitwidth, builtin.DefaultHamtBitwidth) 354 if err != nil { 355 return nil, xerrors.Errorf("failed to load deals by epoch: %w", err) 356 } 357 m.dealsByEpoch = dbe 358 } 359 360 m.nextDealId = m.st.NextID 361 362 return m, nil 363 } 364 365 func (m *marketStateMutation) withDealProposals(permit MarketStateMutationPermission) *marketStateMutation { 366 m.proposalPermit = permit 367 return m 368 } 369 370 func (m *marketStateMutation) withDealStates(permit MarketStateMutationPermission) *marketStateMutation { 371 m.statePermit = permit 372 return m 373 } 374 375 func (m *marketStateMutation) withEscrowTable(permit MarketStateMutationPermission) *marketStateMutation { 376 m.escrowPermit = permit 377 return m 378 } 379 380 func (m *marketStateMutation) withLockedTable(permit MarketStateMutationPermission) *marketStateMutation { 381 m.lockedPermit = permit 382 return m 383 } 384 385 func (m *marketStateMutation) withPendingProposals(permit MarketStateMutationPermission) *marketStateMutation { 386 m.pendingPermit = permit 387 return m 388 } 389 390 func (m *marketStateMutation) withDealsByEpoch(permit MarketStateMutationPermission) *marketStateMutation { 391 m.dpePermit = permit 392 return m 393 } 394 395 func (m *marketStateMutation) commitState() error { 396 var err error 397 if m.proposalPermit == WritePermission { 398 if m.st.Proposals, err = m.dealProposals.Root(); err != nil { 399 return xerrors.Errorf("failed to flush deal dealProposals: %w", err) 400 } 401 } 402 403 if m.statePermit == WritePermission { 404 if m.st.States, err = m.dealStates.Root(); err != nil { 405 return xerrors.Errorf("failed to flush deal states: %w", err) 406 } 407 } 408 409 if m.lockedPermit == WritePermission { 410 if m.st.LockedTable, err = m.lockedTable.Root(); err != nil { 411 return xerrors.Errorf("failed to flush locked table: %w", err) 412 } 413 m.st.TotalClientLockedCollateral = m.totalClientLockedCollateral.Copy() 414 m.st.TotalProviderLockedCollateral = m.totalProviderLockedCollateral.Copy() 415 m.st.TotalClientStorageFee = m.totalClientStorageFee.Copy() 416 } 417 418 if m.escrowPermit == WritePermission { 419 if m.st.EscrowTable, err = m.escrowTable.Root(); err != nil { 420 return xerrors.Errorf("failed to flush escrow table: %w", err) 421 } 422 } 423 424 if m.pendingPermit == WritePermission { 425 if m.st.PendingProposals, err = m.pendingDeals.Root(); err != nil { 426 return xerrors.Errorf("failed to flush pending deals: %w", err) 427 } 428 } 429 430 if m.dpePermit == WritePermission { 431 if m.st.DealOpsByEpoch, err = m.dealsByEpoch.Root(); err != nil { 432 return xerrors.Errorf("failed to flush deals by epoch: %w", err) 433 } 434 } 435 436 m.st.NextID = m.nextDealId 437 return nil 438 }