Skip to content

Commit 3dfc8c7

Browse files
authored
feat: record multisig approvals (#389)
* feat: record multisig approvals
1 parent c09eb82 commit 3dfc8c7

File tree

9 files changed

+539
-94
lines changed

9 files changed

+539
-94
lines changed

chain/indexer.go

Lines changed: 110 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/filecoin-project/sentinel-visor/metrics"
1919
"github.com/filecoin-project/sentinel-visor/model"
2020
visormodel "github.com/filecoin-project/sentinel-visor/model/visor"
21+
"github.com/filecoin-project/sentinel-visor/tasks/messages"
22+
"github.com/filecoin-project/sentinel-visor/tasks/msapprovals"
2123
)
2224

2325
const (
@@ -31,6 +33,7 @@ const (
3133
BlocksTask = "blocks" // task that extracts block data
3234
MessagesTask = "messages" // task that extracts message data
3335
ChainEconomicsTask = "chaineconomics" // task that extracts chain economics data
36+
MultisigApprovalsTask = "msapprovals" // task that extracts multisig actor approvals
3437
)
3538

3639
var log = logging.Logger("chain")
@@ -39,16 +42,17 @@ var _ TipSetObserver = (*TipSetIndexer)(nil)
3942

4043
// A TipSetWatcher waits for tipsets and persists their block data into a database.
4144
type TipSetIndexer struct {
42-
window time.Duration
43-
storage model.Storage
44-
processors map[string]TipSetProcessor
45-
actorProcessors map[string]ActorProcessor
46-
name string
47-
persistSlot chan struct{}
48-
lastTipSet *types.TipSet
49-
node lens.API
50-
opener lens.APIOpener
51-
closer lens.APICloser
45+
window time.Duration
46+
storage model.Storage
47+
processors map[string]TipSetProcessor
48+
messageProcessors map[string]MessageProcessor
49+
actorProcessors map[string]ActorProcessor
50+
name string
51+
persistSlot chan struct{}
52+
lastTipSet *types.TipSet
53+
node lens.API
54+
opener lens.APIOpener
55+
closer lens.APICloser
5256
}
5357

5458
// A TipSetIndexer extracts block, message and actor state data from a tipset and persists it to storage. Extraction
@@ -57,21 +61,22 @@ type TipSetIndexer struct {
5761
// indexer is used as the reporter in the visor_processing_reports table.
5862
func NewTipSetIndexer(o lens.APIOpener, d model.Storage, window time.Duration, name string, tasks []string) (*TipSetIndexer, error) {
5963
tsi := &TipSetIndexer{
60-
storage: d,
61-
window: window,
62-
name: name,
63-
persistSlot: make(chan struct{}, 1), // allow one concurrent persistence job
64-
processors: map[string]TipSetProcessor{},
65-
actorProcessors: map[string]ActorProcessor{},
66-
opener: o,
64+
storage: d,
65+
window: window,
66+
name: name,
67+
persistSlot: make(chan struct{}, 1), // allow one concurrent persistence job
68+
processors: map[string]TipSetProcessor{},
69+
messageProcessors: map[string]MessageProcessor{},
70+
actorProcessors: map[string]ActorProcessor{},
71+
opener: o,
6772
}
6873

6974
for _, task := range tasks {
7075
switch task {
7176
case BlocksTask:
7277
tsi.processors[BlocksTask] = NewBlockProcessor()
7378
case MessagesTask:
74-
tsi.processors[MessagesTask] = NewMessageProcessor(o)
79+
tsi.messageProcessors[MessagesTask] = messages.NewTask(o)
7580
case ChainEconomicsTask:
7681
tsi.processors[ChainEconomicsTask] = NewChainEconomicsProcessor(o)
7782
case ActorStatesRawTask:
@@ -112,6 +117,8 @@ func NewTipSetIndexer(o lens.APIOpener, d model.Storage, window time.Duration, n
112117
CodeV2: sa2builtin.MultisigActorCodeID,
113118
CodeV3: sa3builtin.MultisigActorCodeID,
114119
})
120+
case MultisigApprovalsTask:
121+
tsi.messageProcessors[MultisigApprovalsTask] = msapprovals.NewTask(o)
115122
default:
116123
return nil, xerrors.Errorf("unknown task: %s", task)
117124
}
@@ -135,6 +142,8 @@ func (t *TipSetIndexer) TipSet(ctx context.Context, ts *types.TipSet) error {
135142
}
136143
defer cancel()
137144

145+
ll := log.With("height", int64(ts.Height()))
146+
138147
start := time.Now()
139148

140149
inFlight := 0
@@ -149,8 +158,8 @@ func (t *TipSetIndexer) TipSet(ctx context.Context, ts *types.TipSet) error {
149158
go t.runProcessor(tctx, p, name, ts, results)
150159
}
151160

152-
// Run each actor processing task concurrently if we have any and we've seen a previous tipset to compare with
153-
if len(t.actorProcessors) > 0 {
161+
// Run each actor or message processing task concurrently if we have any and we've seen a previous tipset to compare with
162+
if len(t.actorProcessors) > 0 || len(t.messageProcessors) > 0 {
154163

155164
// Actor processors perform a diff between two tipsets so we need to keep track of parent and child
156165
var parent, child *types.TipSet
@@ -181,36 +190,65 @@ func (t *TipSetIndexer) TipSet(ctx context.Context, ts *types.TipSet) error {
181190
t.closer = closer
182191
}
183192

184-
changes, err := t.node.StateChangedActors(tctx, parent.ParentState(), child.ParentState())
185-
if err != nil {
186-
187-
terr := xerrors.Errorf("failed to extract actor changes: %w", err)
188-
// We need to report that all actor tasks failed
189-
for name := range t.actorProcessors {
190-
report := &visormodel.ProcessingReport{
191-
Height: int64(ts.Height()),
192-
StateRoot: ts.ParentState().String(),
193-
Reporter: t.name,
194-
Task: name,
195-
StartedAt: start,
196-
CompletedAt: time.Now(),
197-
Status: visormodel.ProcessingStatusError,
198-
ErrorsDetected: terr,
193+
// If we have message processors then extract the messages and receipts
194+
if len(t.messageProcessors) > 0 {
195+
emsgs, err := t.node.GetExecutedMessagesForTipset(ctx, child, parent)
196+
if err == nil {
197+
// Start all the message processors
198+
for name, p := range t.messageProcessors {
199+
inFlight++
200+
go t.runMessageProcessor(tctx, p, name, child, parent, emsgs, results)
201+
}
202+
} else {
203+
ll.Errorw("failed to extract messages", "error", err)
204+
terr := xerrors.Errorf("failed to extract messages: %w", err)
205+
// We need to report that all message tasks failed
206+
for name := range t.messageProcessors {
207+
report := &visormodel.ProcessingReport{
208+
Height: int64(ts.Height()),
209+
StateRoot: ts.ParentState().String(),
210+
Reporter: t.name,
211+
Task: name,
212+
StartedAt: start,
213+
CompletedAt: time.Now(),
214+
Status: visormodel.ProcessingStatusError,
215+
ErrorsDetected: terr,
216+
}
217+
taskOutputs[name] = model.PersistableList{report}
199218
}
200-
taskOutputs[name] = model.PersistableList{report}
201219
}
202-
return terr
203220
}
204221

205-
for name, p := range t.actorProcessors {
206-
inFlight++
207-
go t.runActorProcessor(tctx, p, name, child, parent, changes, results)
222+
// If we have actor processors then find actors that have changed state
223+
if len(t.actorProcessors) > 0 {
224+
changes, err := t.node.StateChangedActors(tctx, parent.ParentState(), child.ParentState())
225+
if err == nil {
226+
for name, p := range t.actorProcessors {
227+
inFlight++
228+
go t.runActorProcessor(tctx, p, name, child, parent, changes, results)
229+
}
230+
} else {
231+
ll.Errorw("failed to extract actor changes", "error", err)
232+
terr := xerrors.Errorf("failed to extract actor changes: %w", err)
233+
// We need to report that all actor tasks failed
234+
for name := range t.actorProcessors {
235+
report := &visormodel.ProcessingReport{
236+
Height: int64(ts.Height()),
237+
StateRoot: ts.ParentState().String(),
238+
Reporter: t.name,
239+
Task: name,
240+
StartedAt: start,
241+
CompletedAt: time.Now(),
242+
Status: visormodel.ProcessingStatusError,
243+
ErrorsDetected: terr,
244+
}
245+
taskOutputs[name] = model.PersistableList{report}
246+
}
247+
}
208248
}
209249
}
210250
}
211251

212-
ll := log.With("height", int64(ts.Height()))
213-
214252
// Wait for all tasks to complete
215253
for inFlight > 0 {
216254
res := <-results
@@ -326,6 +364,28 @@ func (t *TipSetIndexer) runProcessor(ctx context.Context, p TipSetProcessor, nam
326364
}
327365
}
328366

367+
func (t *TipSetIndexer) runMessageProcessor(ctx context.Context, p MessageProcessor, name string, ts, pts *types.TipSet, emsgs []*lens.ExecutedMessage, results chan *TaskResult) {
368+
ctx, _ = tag.New(ctx, tag.Upsert(metrics.TaskType, name))
369+
stats.Record(ctx, metrics.TipsetHeight.M(int64(ts.Height())))
370+
stop := metrics.Timer(ctx, metrics.ProcessingDuration)
371+
defer stop()
372+
373+
data, report, err := p.ProcessMessages(ctx, ts, pts, emsgs)
374+
if err != nil {
375+
stats.Record(ctx, metrics.ProcessingFailure.M(1))
376+
results <- &TaskResult{
377+
Task: name,
378+
Error: err,
379+
}
380+
return
381+
}
382+
results <- &TaskResult{
383+
Task: name,
384+
Report: report,
385+
Data: data,
386+
}
387+
}
388+
329389
func (t *TipSetIndexer) runActorProcessor(ctx context.Context, p ActorProcessor, name string, ts, pts *types.TipSet, actors map[string]types.Actor, results chan *TaskResult) {
330390
ctx, _ = tag.New(ctx, tag.Upsert(metrics.TaskType, name))
331391
stats.Record(ctx, metrics.TipsetHeight.M(int64(ts.Height())))
@@ -384,6 +444,14 @@ type TipSetProcessor interface {
384444
Close() error
385445
}
386446

447+
type MessageProcessor interface {
448+
// ProcessMessages processes messages contained within a tipset. If error is non-nil then the processor encountered a fatal error.
449+
// pts is the tipset containing the messages, ts is the tipset containing the receipts
450+
// Any data returned must be accompanied by a processing report.
451+
ProcessMessages(ctx context.Context, ts *types.TipSet, pts *types.TipSet, emsgs []*lens.ExecutedMessage) (model.Persistable, *visormodel.ProcessingReport, error)
452+
Close() error
453+
}
454+
387455
type ActorProcessor interface {
388456
// ProcessActor processes a set of actors. If error is non-nil then the processor encountered a fatal error.
389457
// Any data returned must be accompanied by a processing report.

lens/lotus/api.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/filecoin-project/go-address"
77
"github.com/filecoin-project/go-bitfield"
88
"github.com/filecoin-project/lotus/api"
9+
builtininit "github.com/filecoin-project/lotus/chain/actors/builtin/init"
910
miner "github.com/filecoin-project/lotus/chain/actors/builtin/miner"
1011
"github.com/filecoin-project/lotus/chain/state"
1112
"github.com/filecoin-project/lotus/chain/types"
@@ -220,6 +221,16 @@ func (aw *APIWrapper) GetExecutedMessagesForTipset(ctx context.Context, ts, pts
220221
return nil, xerrors.Errorf("load parent state tree: %w", err)
221222
}
222223

224+
initActor, err := stateTree.GetActor(builtininit.Address)
225+
if err != nil {
226+
return nil, xerrors.Errorf("getting init actor: %w", err)
227+
}
228+
229+
initActorState, err := builtininit.Load(aw.Store(), initActor)
230+
if err != nil {
231+
return nil, xerrors.Errorf("loading init actor state: %w", err)
232+
}
233+
223234
// Build a lookup of actor codes
224235
actorCodes := map[address.Address]cid.Cid{}
225236
if err := stateTree.ForEach(func(a address.Address, act *types.Actor) error {
@@ -230,7 +241,12 @@ func (aw *APIWrapper) GetExecutedMessagesForTipset(ctx context.Context, ts, pts
230241
}
231242

232243
getActorCode := func(a address.Address) cid.Cid {
233-
c, ok := actorCodes[a]
244+
ra, found, err := initActorState.ResolveAddress(a)
245+
if err != nil || !found {
246+
return cid.Undef
247+
}
248+
249+
c, ok := actorCodes[ra]
234250
if ok {
235251
return c
236252
}

lens/util/repo.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/filecoin-project/go-multistore"
1111
"github.com/filecoin-project/go-state-types/abi"
1212
"github.com/filecoin-project/lotus/api"
13+
builtininit "github.com/filecoin-project/lotus/chain/actors/builtin/init"
1314
"github.com/filecoin-project/lotus/chain/state"
1415
"github.com/filecoin-project/lotus/chain/stmgr"
1516
"github.com/filecoin-project/lotus/chain/store"
@@ -24,14 +25,15 @@ import (
2425
"github.com/filecoin-project/lotus/node/impl"
2526
"github.com/filecoin-project/lotus/node/impl/full"
2627
"github.com/filecoin-project/lotus/node/repo"
27-
"github.com/filecoin-project/sentinel-visor/lens"
2828
"github.com/filecoin-project/specs-actors/actors/runtime/proof"
2929
"github.com/filecoin-project/specs-actors/actors/util/adt"
3030
"github.com/ipfs/go-cid"
3131
cbor "github.com/ipfs/go-ipld-cbor"
3232
peer "github.com/libp2p/go-libp2p-core/peer"
3333
"github.com/urfave/cli/v2"
3434
"golang.org/x/xerrors"
35+
36+
"github.com/filecoin-project/sentinel-visor/lens"
3537
)
3638

3739
type APIOpener struct {
@@ -240,6 +242,16 @@ func GetExecutedMessagesForTipset(ctx context.Context, cs *store.ChainStore, ts,
240242
return nil, xerrors.Errorf("load state tree: %w", err)
241243
}
242244

245+
initActor, err := stateTree.GetActor(builtininit.Address)
246+
if err != nil {
247+
return nil, xerrors.Errorf("getting init actor: %w", err)
248+
}
249+
250+
initActorState, err := builtininit.Load(cs.Store(ctx), initActor)
251+
if err != nil {
252+
return nil, xerrors.Errorf("loading init actor state: %w", err)
253+
}
254+
243255
// Build a lookup of actor codes
244256
actorCodes := map[address.Address]cid.Cid{}
245257
if err := stateTree.ForEach(func(a address.Address, act *types.Actor) error {
@@ -250,7 +262,12 @@ func GetExecutedMessagesForTipset(ctx context.Context, cs *store.ChainStore, ts,
250262
}
251263

252264
getActorCode := func(a address.Address) cid.Cid {
253-
c, ok := actorCodes[a]
265+
ra, found, err := initActorState.ResolveAddress(a)
266+
if err != nil || !found {
267+
return cid.Undef
268+
}
269+
270+
c, ok := actorCodes[ra]
254271
if ok {
255272
return c
256273
}

model/msapprovals/msapprovals.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package msapprovals
2+
3+
import (
4+
"context"
5+
6+
"go.opencensus.io/tag"
7+
8+
"github.com/filecoin-project/sentinel-visor/metrics"
9+
"github.com/filecoin-project/sentinel-visor/model"
10+
)
11+
12+
type MultisigApproval struct {
13+
tableName struct{} `pg:"multisig_approvals"` // nolint: structcheck,unused
14+
Height int64 `pg:",pk,notnull,use_zero"`
15+
StateRoot string `pg:",pk,notnull"`
16+
MultisigID string `pg:",pk,notnull"`
17+
Message string `pg:",pk,notnull"` // cid of message
18+
Method uint64 `pg:",notnull,use_zero"` // method number used for the approval 2=propose, 3=approve
19+
Approver string `pg:",pk,notnull"` // address of signer that triggerd approval
20+
Threshold uint64 `pg:",notnull,use_zero"`
21+
InitialBalance string `pg:"type:numeric,notnull"`
22+
Signers []string `pg:",notnull"`
23+
GasUsed int64 `pg:",use_zero"`
24+
TransactionID int64 `pg:",notnull,use_zero"`
25+
To string `pg:",use_zero"` // address funds will move to in transaction
26+
Value string `pg:"type:numeric,notnull"` // amount of funds moved in transaction
27+
}
28+
29+
func (ma *MultisigApproval) Persist(ctx context.Context, s model.StorageBatch) error {
30+
ctx, _ = tag.New(ctx, tag.Upsert(metrics.Table, "multisig_approvals"))
31+
stop := metrics.Timer(ctx, metrics.PersistDuration)
32+
defer stop()
33+
34+
return s.PersistModel(ctx, ma)
35+
}
36+
37+
type MultisigApprovalList []*MultisigApproval
38+
39+
func (mal MultisigApprovalList) Persist(ctx context.Context, s model.StorageBatch) error {
40+
if len(mal) == 0 {
41+
return nil
42+
}
43+
ctx, _ = tag.New(ctx, tag.Upsert(metrics.Table, "multisig_approvals"))
44+
stop := metrics.Timer(ctx, metrics.PersistDuration)
45+
defer stop()
46+
47+
return s.PersistModel(ctx, mal)
48+
}

0 commit comments

Comments
 (0)