-
Notifications
You must be signed in to change notification settings - Fork 37
/
Copy pathcheck.go
385 lines (338 loc) · 16.4 KB
/
check.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
package v15
import (
"bytes"
"fmt"
"github.com/ipfs/go-cid"
"golang.org/x/xerrors"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/builtin/v15/account"
"github.com/filecoin-project/go-state-types/builtin/v15/cron"
"github.com/filecoin-project/go-state-types/builtin/v15/datacap"
"github.com/filecoin-project/go-state-types/builtin/v15/evm"
init_ "github.com/filecoin-project/go-state-types/builtin/v15/init"
"github.com/filecoin-project/go-state-types/builtin/v15/market"
"github.com/filecoin-project/go-state-types/builtin/v15/miner"
"github.com/filecoin-project/go-state-types/builtin/v15/multisig"
"github.com/filecoin-project/go-state-types/builtin/v15/paych"
"github.com/filecoin-project/go-state-types/builtin/v15/power"
"github.com/filecoin-project/go-state-types/builtin/v15/reward"
"github.com/filecoin-project/go-state-types/builtin/v15/verifreg"
"github.com/filecoin-project/go-state-types/manifest"
)
// Within this code, Go errors are not expected, but are often converted to messages so that execution
// can continue to find more errors rather than fail with no insight.
// Only errors thar are particularly troublesome to recover from should propagate as Go errors.
func CheckStateInvariants(tree *builtin.ActorTree, priorEpoch abi.ChainEpoch, actorCodes map[string]cid.Cid) (*builtin.MessageAccumulator, error) {
acc := &builtin.MessageAccumulator{}
totalFIl := big.Zero()
var initSummary *init_.StateSummary
var cronSummary *cron.StateSummary
var verifregSummary *verifreg.StateSummary
var datacapSummary *datacap.StateSummary
var marketSummary *market.StateSummary
var rewardSummary *reward.StateSummary
var accountSummaries []*account.StateSummary
var powerSummary *power.StateSummary
var paychSummaries []*paych.StateSummary
var multisigSummaries []*multisig.StateSummary
var delegatedAddrs []address.Address
minerSummaries := make(map[address.Address]*miner.StateSummary)
emptyObjectCid, err := builtin.MakeEmptyState()
if err != nil {
return nil, err
}
if err := tree.ForEachV5(func(key address.Address, actor *builtin.ActorV5) error {
acc := acc.WithPrefix("%v ", key) // Intentional shadow
if key.Protocol() != address.ID {
acc.Addf("unexpected address protocol in state tree root: %v", key)
}
totalFIl = big.Add(totalFIl, actor.Balance)
if actor.DelegatedAddress != nil {
acc.Require(actor.DelegatedAddress.Protocol() == address.Delegated, "actor.Address %v is not a delegated address", *actor.DelegatedAddress)
if actor.DelegatedAddress.Protocol() == address.Delegated {
delegatedAddrs = append(delegatedAddrs, *actor.DelegatedAddress)
}
}
switch actor.Code {
case actorCodes[manifest.SystemKey]:
case actorCodes[manifest.InitKey]:
var st init_.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
fmt.Println("init invariant error = ", err)
return err
}
summary, msgs := init_.CheckStateInvariants(&st, tree, actorCodes)
acc.WithPrefix("init: ").AddAll(msgs)
initSummary = summary
case actorCodes[manifest.CronKey]:
var st cron.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := cron.CheckStateInvariants(&st, tree.Store)
acc.WithPrefix("cron: ").AddAll(msgs)
cronSummary = summary
case actorCodes[manifest.AccountKey]:
var st account.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := account.CheckStateInvariants(&st, key)
acc.WithPrefix("account: ").AddAll(msgs)
accountSummaries = append(accountSummaries, summary)
case actorCodes[manifest.PowerKey]:
var st power.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := power.CheckStateInvariants(&st, tree.Store)
acc.WithPrefix("power: ").AddAll(msgs)
powerSummary = summary
case actorCodes[manifest.MinerKey]:
var st miner.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := miner.CheckStateInvariants(&st, tree.Store, actor.Balance)
acc.WithPrefix("miner: ").AddAll(msgs)
minerSummaries[key] = summary
case actorCodes[manifest.MarketKey]:
var st market.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := market.CheckStateInvariants(&st, tree.Store, actor.Balance, priorEpoch)
acc.WithPrefix("market: ").AddAll(msgs)
marketSummary = summary
case actorCodes[manifest.PaychKey]:
var st paych.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := paych.CheckStateInvariants(&st, tree.Store, actor.Balance)
acc.WithPrefix("paych: ").AddAll(msgs)
paychSummaries = append(paychSummaries, summary)
case actorCodes[manifest.MultisigKey]:
var st multisig.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := multisig.CheckStateInvariants(&st, tree.Store)
acc.WithPrefix("multisig: ").AddAll(msgs)
multisigSummaries = append(multisigSummaries, summary)
case actorCodes[manifest.RewardKey]:
var st reward.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := reward.CheckStateInvariants(&st, tree.Store, priorEpoch, actor.Balance)
acc.WithPrefix("reward: ").AddAll(msgs)
rewardSummary = summary
case actorCodes[manifest.VerifregKey]:
var st verifreg.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := verifreg.CheckStateInvariants(&st, tree.Store, priorEpoch)
acc.WithPrefix("verifreg: ").AddAll(msgs)
verifregSummary = summary
case actorCodes[manifest.DatacapKey]:
var st datacap.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
summary, msgs := datacap.CheckStateInvariants(&st, tree.Store)
acc.WithPrefix("datacap: ").AddAll(msgs)
datacapSummary = summary
case actorCodes[manifest.EvmKey]:
var st evm.State
if err := tree.Store.Get(tree.Store.Context(), actor.Head, &st); err != nil {
return err
}
msgs := evm.CheckStateInvariants(&st, tree.Store)
acc.WithPrefix("evm: ").AddAll(msgs)
case actorCodes[manifest.PlaceholderKey]:
acc.Require(actor.Head == emptyObjectCid, "Placeholder actor head %v unequal to emptyObjectCid %v", actor.Head, emptyObjectCid)
case actorCodes[manifest.EthAccountKey]:
acc.Require(actor.Head == emptyObjectCid, "EthAccount actor head %v unequal to emptyObjectCid %v", actor.Head, emptyObjectCid)
case actorCodes[manifest.EamKey]:
acc.Require(actor.Head == emptyObjectCid, "Eam actor head %s unequal to emptyObjectCid %s", actor.Head, emptyObjectCid)
default:
return xerrors.Errorf("unexpected actor code CID %v for address %v", actor.Code, key)
}
return nil
}); err != nil {
return nil, err
}
// Check if all delegated addresses are part of init actor
for _, addr := range delegatedAddrs {
_, found := initSummary.AddrIDs[addr]
acc.Require(found, "delegated address %v not found in init actor map", addr)
}
//
// Perform cross-actor checks from state summaries here.
//
CheckMinersAgainstPower(acc, minerSummaries, powerSummary)
CheckDealStatesAgainstSectors(acc, minerSummaries, marketSummary, priorEpoch)
CheckVerifregAgainstMiners(acc, verifregSummary, minerSummaries)
CheckMarketAgainstVerifreg(acc, verifregSummary, marketSummary)
CheckVerifregAgainstDatacap(acc, verifregSummary, datacapSummary)
_ = initSummary
_ = verifregSummary
_ = cronSummary
_ = marketSummary
_ = rewardSummary
_ = datacapSummary
if !totalFIl.Equals(builtin.TotalFilecoin) {
acc.Addf("total token balance is %v, expected %v", totalFIl, builtin.TotalFilecoin)
}
return acc, nil
}
func CheckMinersAgainstPower(acc *builtin.MessageAccumulator, minerSummaries map[address.Address]*miner.StateSummary, powerSummary *power.StateSummary) {
for addr, minerSummary := range minerSummaries { // nolint:nomaprange
// check claim
claim, ok := powerSummary.Claims[addr]
acc.Require(ok, "miner %v has no power claim", addr)
if ok {
claimPower := miner.NewPowerPair(claim.RawBytePower, claim.QualityAdjPower)
acc.Require(minerSummary.ActivePower.Equals(claimPower),
"miner %v computed active power %v does not match claim %v", addr, minerSummary.ActivePower, claimPower)
acc.Require(minerSummary.WindowPoStProofType == claim.WindowPoStProofType,
"miner seal proof type %d does not match claim proof type %d", minerSummary.WindowPoStProofType, claim.WindowPoStProofType)
}
// check crons
crons, ok := powerSummary.Crons[addr]
if !ok { // with deferred and discontinued crons it is normal for a miner actor to have no cron events
continue
}
var payload miner.CronEventPayload
var provingPeriodCron *power.MinerCronEvent
for _, event := range crons {
err := payload.UnmarshalCBOR(bytes.NewReader(event.Payload))
acc.Require(err == nil, "miner %v registered cron at epoch %d with wrong or corrupt payload",
addr, event.Epoch)
acc.Require(payload.EventType == miner.CronEventProcessEarlyTerminations || payload.EventType == miner.CronEventProvingDeadline,
"miner %v has unexpected cron event type %v", addr, payload.EventType)
if payload.EventType == miner.CronEventProvingDeadline {
if provingPeriodCron != nil {
acc.Require(false, "miner %v has duplicate proving period crons at epoch %d and %d",
addr, provingPeriodCron.Epoch, event.Epoch)
}
provingPeriodCron = &event
}
}
hasProvingPeriodCron := provingPeriodCron != nil
acc.Require(hasProvingPeriodCron == minerSummary.DeadlineCronActive, "miner %v has invalid DeadlineCronActive (%t) for hasProvingPeriodCron status (%t)",
addr, minerSummary.DeadlineCronActive, hasProvingPeriodCron)
acc.Require(provingPeriodCron != nil, "miner %v has no proving period cron", addr)
}
}
func CheckDealStatesAgainstSectors(acc *builtin.MessageAccumulator, minerSummaries map[address.Address]*miner.StateSummary, marketSummary *market.StateSummary, currEpoch abi.ChainEpoch) {
// Check that all active deals are included within a non-terminated sector.
// We cannot check that all deals referenced within a sector are in the market, because deals
// can be terminated independently of the sector in which they are included.
for dealID, deal := range marketSummary.Deals { // nolint:nomaprange
if deal.SectorStartEpoch == abi.ChainEpoch(-1) {
// deal hasn't been activated yet, make no assertions about sector state
continue
}
minerSummary, found := minerSummaries[deal.Provider]
if !found {
acc.Addf("provider %v for deal %d not found among miners", deal.Provider, dealID)
continue
}
sectorDeal, found := minerSummary.Deals[dealID]
if !found {
continue
}
acc.Require(deal.SectorStartEpoch >= sectorDeal.SectorStart,
"deal state start %d does not match sector start %d for miner %v",
deal.SectorStartEpoch, sectorDeal.SectorStart, deal.Provider)
acc.Require(deal.SectorStartEpoch <= sectorDeal.SectorExpiration,
"deal state start %d activated after sector expiration %d for miner %v",
deal.SectorStartEpoch, sectorDeal.SectorExpiration, deal.Provider)
acc.Require(deal.LastUpdatedEpoch <= sectorDeal.SectorExpiration,
"deal state update at %d after sector expiration %d for miner %v",
deal.LastUpdatedEpoch, sectorDeal.SectorExpiration, deal.Provider)
acc.Require(deal.SlashEpoch <= sectorDeal.SectorExpiration,
"deal state slashed at %d after sector expiration %d for miner %v",
deal.SlashEpoch, sectorDeal.SectorExpiration, deal.Provider)
acc.Require((deal.SectorNumber == sectorDeal.SectorNumber) || (deal.SectorNumber == 0 && deal.SlashEpoch != -1) || (deal.SectorNumber == 0 && deal.EndEpoch < currEpoch),
"deal sector number %d does not match sector %d for miner %v (ds: %#v; ss %#v)",
deal.SectorNumber, sectorDeal.SectorNumber, deal.Provider, deal, sectorDeal)
}
// HAMT[ActorID]HAMT[SectorNumber]SectorDealIDs
marketDealToSector := make(map[abi.DealID]abi.SectorID)
for sectorID, dealIDs := range marketSummary.ProviderSectors {
for _, dealID := range dealIDs {
_, found := marketDealToSector[dealID]
acc.Require(!found, "deal %d found in multiple sectors", dealID)
marketDealToSector[dealID] = sectorID
}
}
}
func CheckVerifregAgainstDatacap(acc *builtin.MessageAccumulator, verifregSummary *verifreg.StateSummary, datacapSummary *datacap.StateSummary) {
// Check verifiers and clients are disjoint.
for verifier := range verifregSummary.Verifiers {
actorId, err := address.IDFromAddress(verifier)
acc.RequireNoError(err, "error getting actor ID: %v", err)
_, found := datacapSummary.Balances[abi.ActorID(actorId)]
acc.Require(!found, "verifier %v is also a client", verifier)
}
// Check verifreg token balance matches unclaimed allocations
var pendingAllocationsTotal = big.Zero()
for _, allocation := range verifregSummary.Allocations {
pendingAllocationsTotal = big.Add(pendingAllocationsTotal, big.NewIntUnsigned(uint64(allocation.Size)))
}
pendingAllocationsTotal = big.Mul(pendingAllocationsTotal, verifreg.DataCapGranularity)
verifregId, err := address.IDFromAddress(builtin.VerifiedRegistryActorAddr)
acc.RequireNoError(err, "could not get verifreg ID from address")
verifregBalance, found := datacapSummary.Balances[abi.ActorID(verifregId)]
if !found {
verifregBalance = big.Zero()
}
acc.Require(found, "verifreg not found in datacap actor balances map")
acc.Require(verifregBalance.Equals(pendingAllocationsTotal), "verifreg datacap balance %d does not match pending allocation size %d", verifregBalance, pendingAllocationsTotal)
}
func CheckVerifregAgainstMiners(acc *builtin.MessageAccumulator, verifregSummary *verifreg.StateSummary, minerSummaries map[address.Address]*miner.StateSummary) {
for _, claim := range verifregSummary.Claims {
// all claims are indexed by valid providers
maddr, err := address.NewIDAddress(uint64(claim.Provider))
acc.RequireNoError(err, "error creating ID address: %v", err)
_, ok := minerSummaries[maddr]
acc.Require(ok, "claim provider %s is not found in miner summaries", maddr)
}
}
func CheckMarketAgainstVerifreg(acc *builtin.MessageAccumulator, verifregSummary *verifreg.StateSummary, marketSummary *market.StateSummary) {
// all activated verified deals with claim ids reference a claim in verifreg state
// note that it is possible for claims to exist with no matching deal if the deal expires
for claimId, dealId := range marketSummary.ClaimIdToDealId {
claim, found := verifregSummary.Claims[claimId]
acc.Require(found, "claim %d not found for activated deal %d", claimId, dealId)
info, found := marketSummary.Deals[dealId]
acc.Require(found, "internal invariant error invalid market state references missing deal %d", dealId)
providerId, err := address.IDFromAddress(info.Provider)
acc.RequireNoError(err, "error getting ID from provider address")
acc.Require(abi.ActorID(providerId) == claim.Provider, "mismatches providers %d %d on claim %d and deal %d", providerId, claim.Provider, claimId, dealId)
acc.Require(info.PieceCid == claim.Data, "mismatches piece cid %s %s on claim %d and deal %d", info.PieceCid, claim.Data, claimId, dealId)
}
// all pending deal allocation ids have an associated allocation
// note that it is possible for allocations to exist that don't match any deal
// if they are created from a direct DataCap transfer
for allocationId, dealId := range marketSummary.AllocIdToDealId {
alloc, found := verifregSummary.Allocations[allocationId]
acc.Require(found, "allocation %d not found for pending deal %d", allocationId, dealId)
if !found {
continue
}
info, found := marketSummary.Deals[dealId]
acc.Require(found, "internal invariant error invalid market state references missing deal %d", dealId)
providerId, err := address.IDFromAddress(info.Provider)
acc.RequireNoError(err, "error getting ID from provider address")
acc.Require(abi.ActorID(providerId) == alloc.Provider, "mismatched providers %d %d on alloc %d and deal %d", providerId, alloc.Provider, allocationId, dealId)
acc.Require(info.PieceCid == alloc.Data, "mismatched piece cid %s %s on alloc %d and deal %d", info.PieceCid, alloc.Data, allocationId, dealId)
}
}