github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/builtin/market/testing.go (about) 1 package market 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 7 "github.com/ipfs/go-cid" 8 "github.com/pkg/errors" 9 cbg "github.com/whyrusleeping/cbor-gen" 10 11 "github.com/filecoin-project/go-address" 12 "github.com/filecoin-project/go-state-types/abi" 13 "github.com/filecoin-project/go-state-types/big" 14 15 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 16 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 17 ) 18 19 type DealSummary struct { 20 Provider address.Address 21 StartEpoch abi.ChainEpoch 22 EndEpoch abi.ChainEpoch 23 SectorStartEpoch abi.ChainEpoch 24 LastUpdatedEpoch abi.ChainEpoch 25 SlashEpoch abi.ChainEpoch 26 } 27 28 type StateSummary struct { 29 Deals map[abi.DealID]*DealSummary 30 PendingProposalCount uint64 31 DealStateCount uint64 32 LockTableCount uint64 33 DealOpEpochCount uint64 34 DealOpCount uint64 35 } 36 37 // Checks internal invariants of market state. 38 func CheckStateInvariants(st *State, store adt.Store, balance abi.TokenAmount, currEpoch abi.ChainEpoch) (*StateSummary, *builtin.MessageAccumulator) { 39 acc := &builtin.MessageAccumulator{} 40 41 acc.Require( 42 st.TotalClientLockedCollateral.GreaterThanEqual(big.Zero()), 43 "negative total client locked collateral: %v", st.TotalClientLockedCollateral) 44 45 acc.Require( 46 st.TotalProviderLockedCollateral.GreaterThanEqual(big.Zero()), 47 "negative total provider locked collateral: %v", st.TotalClientLockedCollateral) 48 49 acc.Require( 50 st.TotalClientStorageFee.GreaterThanEqual(big.Zero()), 51 "negative total client storage fee: %v", st.TotalClientLockedCollateral) 52 53 // 54 // Proposals 55 // 56 57 proposalCids := make(map[cid.Cid]struct{}) 58 maxDealID := int64(-1) 59 proposalStats := make(map[abi.DealID]*DealSummary) 60 expectedDealOps := make(map[abi.DealID]struct{}) 61 totalProposalCollateral := abi.NewTokenAmount(0) 62 63 if proposals, err := adt.AsArray(store, st.Proposals, ProposalsAmtBitwidth); err != nil { 64 acc.Addf("error loading proposals: %v", err) 65 } else { 66 var proposal DealProposal 67 err = proposals.ForEach(&proposal, func(dealID int64) error { 68 pcid, err := proposal.Cid() 69 if err != nil { 70 return err 71 } 72 73 if proposal.StartEpoch >= currEpoch { 74 expectedDealOps[abi.DealID(dealID)] = struct{}{} 75 } 76 77 // keep some state 78 proposalCids[pcid] = struct{}{} 79 if dealID > maxDealID { 80 maxDealID = dealID 81 } 82 proposalStats[abi.DealID(dealID)] = &DealSummary{ 83 Provider: proposal.Provider, 84 StartEpoch: proposal.StartEpoch, 85 EndEpoch: proposal.EndEpoch, 86 SectorStartEpoch: abi.ChainEpoch(-1), 87 LastUpdatedEpoch: abi.ChainEpoch(-1), 88 SlashEpoch: abi.ChainEpoch(-1), 89 } 90 91 totalProposalCollateral = big.Sum(totalProposalCollateral, proposal.ClientCollateral, proposal.ProviderCollateral) 92 93 acc.Require(proposal.Client.Protocol() == address.ID, "client address for deal %d is not an ID address", dealID) 94 acc.Require(proposal.Provider.Protocol() == address.ID, "provider address for deal %d is not an ID address", dealID) 95 return nil 96 }) 97 acc.RequireNoError(err, "error iterating proposals") 98 } 99 100 // next id should be higher than any existing deal 101 acc.Require(int64(st.NextID) > maxDealID, "next id, %d, is not greater than highest id in proposals, %d", st.NextID, maxDealID) 102 103 // 104 // Deal States 105 // 106 107 dealStateCount := uint64(0) 108 if dealStates, err := adt.AsArray(store, st.States, StatesAmtBitwidth); err != nil { 109 acc.Addf("error loading deal states: %v", err) 110 } else { 111 var dealState DealState 112 err = dealStates.ForEach(&dealState, func(dealID int64) error { 113 acc.Require( 114 dealState.SectorStartEpoch >= 0, 115 "deal %d state start epoch undefined: %v", dealID, dealState) 116 117 acc.Require( 118 dealState.LastUpdatedEpoch == epochUndefined || dealState.LastUpdatedEpoch >= dealState.SectorStartEpoch, 119 "deal %d state last updated before sector start: %v", dealID, dealState) 120 121 acc.Require( 122 dealState.LastUpdatedEpoch == epochUndefined || dealState.LastUpdatedEpoch <= currEpoch, 123 "deal %d last updated epoch %d after current %d", dealID, dealState.LastUpdatedEpoch, currEpoch) 124 125 acc.Require( 126 dealState.SlashEpoch == epochUndefined || dealState.SlashEpoch >= dealState.SectorStartEpoch, 127 "deal %d state slashed before sector start: %v", dealID, dealState) 128 129 acc.Require( 130 dealState.SlashEpoch == epochUndefined || dealState.SlashEpoch <= currEpoch, 131 "deal %d state slashed after current epoch %d: %v", dealID, currEpoch, dealState) 132 133 stats, found := proposalStats[abi.DealID(dealID)] 134 if !found { 135 acc.Addf("deal proposal %d for deal state not found", dealID) 136 } else { 137 stats.SectorStartEpoch = dealState.SectorStartEpoch 138 stats.LastUpdatedEpoch = dealState.LastUpdatedEpoch 139 stats.SlashEpoch = dealState.SlashEpoch 140 } 141 142 dealStateCount++ 143 return nil 144 }) 145 acc.RequireNoError(err, "error iterating deal states") 146 } 147 148 // 149 // Pending Proposals 150 // 151 152 pendingProposalCount := uint64(0) 153 if pendingProposals, err := adt.AsMap(store, st.PendingProposals, builtin.DefaultHamtBitwidth); err != nil { 154 acc.Addf("error loading pending proposals: %v", err) 155 } else { 156 err = pendingProposals.ForEach(nil, func(key string) error { 157 proposalCID, err := cid.Parse([]byte(key)) 158 if err != nil { 159 return err 160 } 161 162 _, found := proposalCids[proposalCID] 163 acc.Require(found, "pending proposal with cid %v not found within proposals %v", proposalCID, pendingProposals) 164 165 pendingProposalCount++ 166 return nil 167 }) 168 acc.RequireNoError(err, "error iterating pending proposals") 169 } 170 171 // 172 // Escrow Table and Locked Table 173 // 174 175 lockTableCount := uint64(0) 176 escrowTable, err := adt.AsBalanceTable(store, st.EscrowTable) 177 acc.RequireNoError(err, "error loading escrow table") 178 lockTable, err := adt.AsBalanceTable(store, st.LockedTable) 179 acc.RequireNoError(err, "error loading locked table") 180 if escrowTable != nil && lockTable != nil { 181 var lockedAmount abi.TokenAmount 182 lockedTotal := abi.NewTokenAmount(0) 183 err = (*adt.Map)(lockTable).ForEach(&lockedAmount, func(key string) error { 184 addr, err := address.NewFromBytes([]byte(key)) 185 if err != nil { 186 return err 187 } 188 lockedTotal = big.Add(lockedTotal, lockedAmount) 189 190 // every entry in locked table should have a corresponding entry in escrow table that is at least as high 191 escrowAmount, err := escrowTable.Get(addr) 192 if err != nil { 193 return err 194 } 195 acc.Require(escrowAmount.GreaterThanEqual(lockedAmount), 196 "locked funds for %s, %s, greater than escrow amount, %s", addr, lockedAmount, escrowAmount) 197 198 lockTableCount++ 199 return nil 200 }) 201 acc.RequireNoError(err, "error iterating locked table") 202 203 // lockTable total should be sum of client and provider locked plus client storage fee 204 expectedLockTotal := big.Sum(st.TotalProviderLockedCollateral, st.TotalClientLockedCollateral, st.TotalClientStorageFee) 205 acc.Require(lockedTotal.Equals(expectedLockTotal), 206 "locked total, %s, does not sum to provider locked, %s, client locked, %s, and client storage fee, %s", 207 lockedTotal, st.TotalProviderLockedCollateral, st.TotalClientLockedCollateral, st.TotalClientStorageFee) 208 209 // assert escrow <= actor balance 210 // lockTable item <= escrow item and escrowTotal <= balance implies lockTable total <= balance 211 escrowTotal, err := escrowTable.Total() 212 acc.RequireNoError(err, "error calculating escrow total") 213 acc.Require(escrowTotal.LessThanEqual(balance), "escrow total, %v, greater than actor balance, %v", escrowTotal, balance) 214 acc.Require(escrowTotal.GreaterThanEqual(totalProposalCollateral), "escrow total, %v, less than sum of proposal collateral, %v", escrowTotal, totalProposalCollateral) 215 } 216 217 // 218 // Deal Ops by Epoch 219 // 220 221 dealOpEpochCount := uint64(0) 222 dealOpCount := uint64(0) 223 if dealOps, err := AsSetMultimap(store, st.DealOpsByEpoch, builtin.DefaultHamtBitwidth, builtin.DefaultHamtBitwidth); err != nil { 224 acc.Addf("error loading deal ops: %v", err) 225 } else { 226 // get into internals just to iterate through full data structure 227 var setRoot cbg.CborCid 228 err = dealOps.mp.ForEach(&setRoot, func(key string) error { 229 epoch, err := binary.ReadUvarint(bytes.NewReader([]byte(key))) 230 if err != nil { 231 return errors.Wrapf(err, "deal ops has key that is not an int: %s", key) 232 } 233 234 dealOpEpochCount++ 235 return dealOps.ForEach(abi.ChainEpoch(epoch), func(id abi.DealID) error { 236 _, found := proposalStats[id] 237 acc.Require(found, "deal op found for deal id %d with missing proposal at epoch %d", id, epoch) 238 delete(expectedDealOps, id) 239 dealOpCount++ 240 return nil 241 }) 242 }) 243 acc.RequireNoError(err, "error iterating deal ops") 244 } 245 246 acc.Require(len(expectedDealOps) == 0, "missing deal ops for proposals: %v", expectedDealOps) 247 248 return &StateSummary{ 249 Deals: proposalStats, 250 PendingProposalCount: pendingProposalCount, 251 DealStateCount: dealStateCount, 252 LockTableCount: lockTableCount, 253 DealOpEpochCount: dealOpEpochCount, 254 DealOpCount: dealOpCount, 255 }, acc 256 }