From baaefdfefbf00ed61002bf5b5c3eb93afc52e71a Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 11 Jan 2025 18:25:04 +1100 Subject: [PATCH 1/8] feat(exec): dump intermediate cache blocks from FVM exec in StateReplay * Plumb through dump_cache from fvm4 to access intermediate blocks: - https://github.com/filecoin-project/filecoin-ffi/pull/512 - https://github.com/filecoin-project/ref-fvm/pull/2101 * Enable cache dumping in StateReplay with LOTUS_REPLAY_DUMP_CACHED_BLOCKS * Add optional "Blocks" field InvocResult * Handle ExecutionEvent::Log's and add "Logs" field to ExecutionTrace * Dump intermediate cache blocks to CAR in /tmp when they appear while using `lotus-shed msg --exec-trace`. --- api/api_full.go | 6 + build/openrpc/full.json | 129 +++++++++++++++++++- build/openrpc/gateway.json | 86 ++++++++++++- chain/consensus/compute_state.go | 27 +++- chain/stmgr/call.go | 4 +- chain/stmgr/execute.go | 4 +- chain/stmgr/stmgr.go | 3 +- chain/types/cbor_gen.go | 65 +++++++++- chain/types/execresult.go | 1 + chain/vm/execution.go | 5 + chain/vm/fvm.go | 8 ++ chain/vm/vm.go | 4 + chain/vm/vmi.go | 4 + cmd/lotus-shed/msg.go | 47 +++++++ conformance/driver.go | 1 + documentation/en/api-v0-methods.md | 54 +++++++- documentation/en/api-v1-unstable-methods.md | 54 +++++++- node/impl/full/state.go | 40 +++++- 18 files changed, 506 insertions(+), 36 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 855a6b8111e..4b2f0675c46 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -1270,6 +1270,12 @@ type InvocResult struct { ExecutionTrace types.ExecutionTrace Error string Duration time.Duration + CachedBlocks []Block `json:",omitempty"` +} + +type Block struct { + Cid cid.Cid + Data []byte } type IpldObject struct { diff --git a/build/openrpc/full.json b/build/openrpc/full.json index 7ec902fb2cc..e5cb2590759 100644 --- a/build/openrpc/full.json +++ b/build/openrpc/full.json @@ -17658,16 +17658,49 @@ "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ], "additionalProperties": false, "properties": { + "CachedBlocks": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Data": { + "media": { + "binaryEncoding": "base64" + }, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, "Duration": { "title": "number", "type": "number" @@ -17742,6 +17775,12 @@ }, "type": "object" }, + "Logs": { + "items": { + "type": "string" + }, + "type": "array" + }, "Msg": { "additionalProperties": false, "properties": { @@ -18353,12 +18392,26 @@ "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ] } @@ -18373,6 +18426,25 @@ "items": { "additionalProperties": false, "properties": { + "CachedBlocks": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Data": { + "media": { + "binaryEncoding": "base64" + }, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, "Duration": { "title": "number", "type": "number" @@ -18447,6 +18519,12 @@ }, "type": "object" }, + "Logs": { + "items": { + "type": "string" + }, + "type": "array" + }, "Msg": { "additionalProperties": false, "properties": { @@ -23704,16 +23782,49 @@ "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ], "additionalProperties": false, "properties": { + "CachedBlocks": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Data": { + "media": { + "binaryEncoding": "base64" + }, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, "Duration": { "title": "number", "type": "number" @@ -23788,6 +23899,12 @@ }, "type": "object" }, + "Logs": { + "items": { + "type": "string" + }, + "type": "array" + }, "Msg": { "additionalProperties": false, "properties": { diff --git a/build/openrpc/gateway.json b/build/openrpc/gateway.json index 4710aa2c6f7..c1d89585f4b 100644 --- a/build/openrpc/gateway.json +++ b/build/openrpc/gateway.json @@ -8483,16 +8483,49 @@ "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ], "additionalProperties": false, "properties": { + "CachedBlocks": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Data": { + "media": { + "binaryEncoding": "base64" + }, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, "Duration": { "title": "number", "type": "number" @@ -8567,6 +8600,12 @@ }, "type": "object" }, + "Logs": { + "items": { + "type": "string" + }, + "type": "array" + }, "Msg": { "additionalProperties": false, "properties": { @@ -11197,16 +11236,49 @@ "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ], "additionalProperties": false, "properties": { + "CachedBlocks": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Data": { + "media": { + "binaryEncoding": "base64" + }, + "type": "string" + } + }, + "type": "object" + }, + "type": "array" + }, "Duration": { "title": "number", "type": "number" @@ -11281,6 +11353,12 @@ }, "type": "object" }, + "Logs": { + "items": { + "type": "string" + }, + "type": "array" + }, "Msg": { "additionalProperties": false, "properties": { diff --git a/chain/consensus/compute_state.go b/chain/consensus/compute_state.go index 51ebb51371e..46576b47f0b 100644 --- a/chain/consensus/compute_state.go +++ b/chain/consensus/compute_state.go @@ -79,7 +79,8 @@ type FilecoinBlockMessages struct { WinCount int64 } -func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, +func (t *TipSetExecutor) ApplyBlocks( + ctx context.Context, sm *stmgr.StateManager, parentEpoch abi.ChainEpoch, pstate cid.Cid, @@ -88,8 +89,10 @@ func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, r rand.Rand, em stmgr.ExecMonitor, vmTracing bool, + cacheStore blockstore.Blockstore, baseFee abi.TokenAmount, - ts *types.TipSet) (cid.Cid, cid.Cid, error) { + ts *types.TipSet, +) (cid.Cid, cid.Cid, error) { done := metrics.Timer(ctx, metrics.VMApplyBlocksTotal) defer done() @@ -240,6 +243,13 @@ func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, if em != nil { if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { + log.Debugw("ApplyBlocks ExecMonitor#MessageApplied callback failed", "error", err) + if cacheStore != nil { + log.Debug("Dumping vm cache blocks to provided cacheStore") + if err := vmi.DumpCache(cacheStore); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) + } + } return cid.Undef, cid.Undef, err } } @@ -296,6 +306,13 @@ func (t *TipSetExecutor) ApplyBlocks(ctx context.Context, } } + if cacheStore != nil { + log.Debug("Dumping vm cache blocks to provided cacheStore") + if err := vmi.DumpCache(cacheStore); err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) + } + } + st, err := vmi.Flush(ctx) if err != nil { return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) @@ -316,7 +333,9 @@ func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, sm *stmgr.StateManager, ts *types.TipSet, em stmgr.ExecMonitor, - vmTracing bool) (stateroot cid.Cid, rectsroot cid.Cid, err error) { + vmTracing bool, + cacheStore blockstore.Blockstore, +) (stateroot cid.Cid, rectsroot cid.Cid, err error) { ctx, span := trace.StartSpan(ctx, "computeTipSetState") defer span.End() @@ -364,7 +383,7 @@ func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, } baseFee := blks[0].ParentBaseFee - return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, vmTracing, baseFee, ts) + return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, vmTracing, cacheStore, baseFee, ts) } func (t *TipSetExecutor) StoreEventsAMT(ctx context.Context, cs *store.ChainStore, events []types.Event) (cid.Cid, error) { diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 90c193b95f8..7fee85a8b41 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -292,12 +292,12 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr var errHaltExecution = fmt.Errorf("halt") -func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid) (*types.Message, *vm.ApplyRet, error) { +func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid, cacheStore blockstore.Blockstore) (*types.Message, *vm.ApplyRet, error) { var finder messageFinder // message to find finder.mcid = mcid - _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true) + _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true, cacheStore) if err != nil && !errors.Is(err, errHaltExecution) { return nil, nil, xerrors.Errorf("unexpected error during execution: %w", err) } diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index 985218ef467..ecf956b117a 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -82,7 +82,7 @@ func (sm *StateManager) tipSetState(ctx context.Context, ts *types.TipSet, recom } } - st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor, false) + st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor, false, nil) if err != nil { return cid.Undef, cid.Undef, err } @@ -136,7 +136,7 @@ func tryLookupTipsetState(ctx context.Context, cs *store.ChainStore, ts *types.T } func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { - st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em, true) + st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em, true, nil) return st, err } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 5b227fe922e..95b761f8f29 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -22,6 +22,7 @@ import ( "github.com/filecoin-project/specs-actors/v8/actors/migration/nv16" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/actors/adt" _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" @@ -119,7 +120,7 @@ func (m *migrationResultCache) Delete(ctx context.Context, root cid.Cid) { type Executor interface { NewActorRegistry() *vm.ActorRegistry - ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor, vmTracing bool) (stateroot cid.Cid, rectsroot cid.Cid, err error) + ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor, vmTracing bool, cacheStore blockstore.Blockstore) (stateroot cid.Cid, rectsroot cid.Cid, err error) } type StateManager struct { diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index 4c13597d075..632f4309a26 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -2823,7 +2823,7 @@ func (t *ReturnTrace) UnmarshalCBOR(r io.Reader) (err error) { return nil } -var lengthBufExecutionTrace = []byte{133} +var lengthBufExecutionTrace = []byte{134} func (t *ExecutionTrace) MarshalCBOR(w io.Writer) error { if t == nil { @@ -2880,6 +2880,28 @@ func (t *ExecutionTrace) MarshalCBOR(w io.Writer) error { return err } + } + + // t.Logs ([]string) (slice) + if len(t.Logs) > 1000000000 { + return xerrors.Errorf("Slice value in field t.Logs was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.Logs))); err != nil { + return err + } + for _, v := range t.Logs { + if len(v) > 8192 { + return xerrors.Errorf("Value in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajTextString, uint64(len(v))); err != nil { + return err + } + if _, err := cw.WriteString(string(v)); err != nil { + return err + } + } return nil } @@ -2903,7 +2925,7 @@ func (t *ExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { return fmt.Errorf("cbor input should be of type array") } - if extra != 5 { + if extra != 6 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -3030,5 +3052,44 @@ func (t *ExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { } } + // t.Logs ([]string) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 1000000000 { + return fmt.Errorf("t.Logs: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.Logs = make([]string, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + sval, err := cbg.ReadStringWithMax(cr, 8192) + if err != nil { + return err + } + + t.Logs[i] = string(sval) + } + + } + } return nil } diff --git a/chain/types/execresult.go b/chain/types/execresult.go index 99bbb6ece9a..c32a4db6a96 100644 --- a/chain/types/execresult.go +++ b/chain/types/execresult.go @@ -45,6 +45,7 @@ type ExecutionTrace struct { InvokedActor *ActorTrace `json:",omitempty"` GasCharges []*GasTrace `cborgen:"maxlen=1000000000"` Subcalls []ExecutionTrace `cborgen:"maxlen=1000000000"` + Logs []string `cborgen:"maxlen=1000000000" json:",omitempty"` } func (et ExecutionTrace) SumGas() GasTrace { diff --git a/chain/vm/execution.go b/chain/vm/execution.go index 4fb626f4390..fce917427a1 100644 --- a/chain/vm/execution.go +++ b/chain/vm/execution.go @@ -10,6 +10,7 @@ import ( "go.opencensus.io/stats" "go.opencensus.io/tag" + "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/metrics" ) @@ -58,6 +59,10 @@ func (e *vmExecutor) Flush(ctx context.Context) (cid.Cid, error) { return e.vmi.Flush(ctx) } +func (e *vmExecutor) DumpCache(bs blockstore.Blockstore) error { + return e.vmi.DumpCache(bs) +} + type executionToken struct { lane ExecutionLane reserved int diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index 77103d31d8d..6190dbbaa00 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -538,6 +538,10 @@ func (vm *FVM) Flush(ctx context.Context) (cid.Cid, error) { return vm.fvm.Flush() } +func (vm *FVM) DumpCache(cacheStore blockstore.Blockstore) error { + return vm.fvm.DumpCache(cacheStore) +} + type dualExecutionFVM struct { main *FVM debug *FVM @@ -608,6 +612,10 @@ func (vm *dualExecutionFVM) Flush(ctx context.Context) (cid.Cid, error) { return vm.main.Flush(ctx) } +func (vm *dualExecutionFVM) DumpCache(cacheStore blockstore.Blockstore) error { + return vm.main.DumpCache(cacheStore) +} + // Passing this as a pointer of structs has proven to be an enormous PiTA; hence this code. type xRedirect struct{ from, to cid.Cid } type xMapping struct{ redirects []xRedirect } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index fabb5e5ebd4..a9ba781dd3f 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -686,6 +686,10 @@ func (vm *LegacyVM) Flush(ctx context.Context) (cid.Cid, error) { return root, nil } +func (vm *LegacyVM) DumpCache(_ blockstore.Blockstore) error { + return fmt.Errorf("not supported") +} + // ActorStore gets the buffered blockstore associated with the LegacyVM. This includes any temporary // blocks produced during this LegacyVM's execution. func (vm *LegacyVM) ActorStore(ctx context.Context) adt.Store { diff --git a/chain/vm/vmi.go b/chain/vm/vmi.go index 042621ca2d4..56ba6f1a5bc 100644 --- a/chain/vm/vmi.go +++ b/chain/vm/vmi.go @@ -9,6 +9,7 @@ import ( "github.com/filecoin-project/go-state-types/network" + bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/types" ) @@ -35,6 +36,9 @@ type Interface interface { ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) // Flush all buffered objects into the state store provided to the VM at construction. Flush(ctx context.Context) (cid.Cid, error) + // Dump the contents of the caching blockstore to the provided blockstore. This will include the + // final state tree as well as any intermediate objects created during messagae execution. + DumpCache(bs bstore.Blockstore) error } // WARNING: You will not affect your node's execution by misusing this feature, but you will confuse yourself thoroughly! diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 4de7789a85c..0a8e77c185e 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -5,12 +5,17 @@ import ( "encoding/base64" "encoding/hex" "encoding/json" + "errors" "fmt" "io" + "os" + "path" "sort" "github.com/fatih/color" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + carbstore "github.com/ipld/go-car/v2/blockstore" "github.com/urfave/cli/v2" "golang.org/x/xerrors" @@ -79,6 +84,23 @@ var msgCmd = &cli.Command{ return xerrors.Errorf("replay call failed: %w", err) } + /* + var fixSealPrice func(trace types.ExecutionTrace) + fixSealPrice = func(trace types.ExecutionTrace) { + for i := range trace.GasCharges { + if trace.GasCharges[i].Name == "OnVerifySeal" && trace.GasCharges[i].ComputeGas == 2000 { + // should be 42M + trace.GasCharges[i].ComputeGas = 42_000_000 + trace.GasCharges[i].TotalGas += 42_000_000 - 2000 + } + } + for i := range trace.Subcalls { + fixSealPrice(trace.Subcalls[i]) + } + } + fixSealPrice(res.ExecutionTrace) + */ + if cctx.Bool("exec-trace") { // Print the execution trace color.Green("Execution trace:") @@ -89,6 +111,31 @@ var msgCmd = &cli.Command{ fmt.Println(string(trace)) fmt.Println() + if res.CachedBlocks != nil { + cachedBlocksFile := path.Join(os.TempDir(), msg.Cid().String()+".car") + if _, err := os.Stat(cachedBlocksFile); !errors.Is(err, os.ErrNotExist) { + return xerrors.Errorf("cached blocks file %s already exists: %w", cachedBlocksFile, err) + } + bs, err := carbstore.OpenReadWrite(cachedBlocksFile, nil, carbstore.WriteAsCarV1(true)) + if err != nil { + return xerrors.Errorf("opening cached blocks file: %w", err) + } + for _, b := range res.CachedBlocks { + bc, err := blocks.NewBlockWithCid(b.Data, b.Cid) + if err != nil { + return xerrors.Errorf("creating cached block: %w", err) + } + if err := bs.Put(ctx, bc); err != nil { + return xerrors.Errorf("writing cached block: %w", err) + } + } + if err := bs.Close(); err != nil { + return xerrors.Errorf("closing cached blocks file: %w", err) + } + color.Green("Cached blocks written to %s", cachedBlocksFile) + fmt.Println() + } + color.Green("Receipt:") fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode) fmt.Printf("Return: %x\n", res.MsgRct.Return) diff --git a/conformance/driver.go b/conformance/driver.go index 15ae567063a..21f4628f984 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -175,6 +175,7 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params params.Rand, recordOutputs, true, + nil, params.BaseFee, nil, ) diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index 8fff5d24bbc..e65018eaa85 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -3872,12 +3872,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ``` @@ -4134,12 +4148,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ] } @@ -5601,12 +5629,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ``` diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 4eae4a2a280..5f4897a37f0 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -6152,12 +6152,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ``` @@ -6414,12 +6428,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ] } @@ -8102,12 +8130,26 @@ Response: "tt": 60000000000 } ], - "Subcalls": null + "Subcalls": null, + "Logs": [ + "string value" + ] } + ], + "Logs": [ + "string value" ] }, "Error": "string value", - "Duration": 60000000000 + "Duration": 60000000000, + "CachedBlocks": [ + { + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Data": "Ynl0ZSBhcnJheQ==" + } + ] } ``` diff --git a/node/impl/full/state.go b/node/impl/full/state.go index f3f0df63d2a..6f4a9c09fc4 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -7,7 +7,9 @@ import ( "errors" "fmt" "math" + "os" "strconv" + "strings" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" @@ -29,6 +31,7 @@ import ( market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin" @@ -106,6 +109,10 @@ type StateAPI struct { TsExec stmgr.Executor } +const ReplayDumpCachedBlocksKey = "LOTUS_REPLAY_DUMP_CACHED_BLOCKS" + +var replayDumpCachedBlocks bool + func (a *StateAPI) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState()) } @@ -457,7 +464,11 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. } } - m, r, err := a.StateManager.Replay(ctx, ts, msgToReplay) + var cacheStore blockstore.Blockstore + if replayDumpCachedBlocks { + cacheStore = blockstore.NewMemory() + } + m, r, err := a.StateManager.Replay(ctx, ts, msgToReplay, cacheStore) if err != nil { return nil, err } @@ -467,7 +478,7 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. errstr = r.ActorErr.Error() } - return &api.InvocResult{ + result := &api.InvocResult{ MsgCid: msgToReplay, Msg: m, MsgRct: &r.MessageReceipt, @@ -475,7 +486,18 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. ExecutionTrace: r.ExecutionTrace, Error: errstr, Duration: r.Duration, - }, nil + } + if replayDumpCachedBlocks { + bs := cacheStore.(blockstore.MemBlockstore) + result.CachedBlocks = make([]api.Block, 0, len(bs)) + for _, blk := range bs { + result.CachedBlocks = append(result.CachedBlocks, api.Block{ + Cid: blk.Cid(), + Data: blk.RawData(), + }) + } + } + return result, nil } func (m *StateModule) StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (a *types.Actor, err error) { @@ -2097,3 +2119,15 @@ func (a *StateAPI) StateGetNetworkParams(ctx context.Context) (*api.NetworkParam }, }, nil } + +func init() { + replayDumpCachedBlocks = (func() bool { + v, _ := os.LookupEnv(ReplayDumpCachedBlocksKey) + switch strings.TrimSpace(strings.ToLower(v)) { + case "", "0", "false", "no": // Consider these values as "do not enable". + return false + default: + return true + } + })() +} From 8cdc350f1d2ea6c808cbbaf51125f3950e558360 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 11 Jan 2025 20:33:32 +1100 Subject: [PATCH 2/8] fixup! feat(exec): dump intermediate cache blocks from FVM exec in StateReplay --- cmd/lotus-shed/block-matcher.go | 278 ++++++++++++++++++++++++++++++++ cmd/lotus-shed/msg.go | 138 +++++++++++++--- 2 files changed, 394 insertions(+), 22 deletions(-) create mode 100644 cmd/lotus-shed/block-matcher.go diff --git a/cmd/lotus-shed/block-matcher.go b/cmd/lotus-shed/block-matcher.go new file mode 100644 index 00000000000..1d8c86776b8 --- /dev/null +++ b/cmd/lotus-shed/block-matcher.go @@ -0,0 +1,278 @@ +package main + +import ( + "bytes" + "context" + "fmt" + + blocks "github.com/ipfs/go-block-format" + cbor "github.com/ipfs/go-ipld-cbor" + "github.com/ipld/go-ipld-prime" + "github.com/ipld/go-ipld-prime/codec/dagcbor" + "github.com/ipld/go-ipld-prime/datamodel" + "github.com/ipld/go-ipld-prime/node/bindnode" + "github.com/ipld/go-ipld-prime/schema" + schemadmt "github.com/ipld/go-ipld-prime/schema/dmt" + schemadsl "github.com/ipld/go-ipld-prime/schema/dsl" + "github.com/ipld/go-ipld-prime/traversal" + cbg "github.com/whyrusleeping/cbor-gen" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-amt-ipld/v4" + "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-hamt-ipld/v3" + gstbuiltin "github.com/filecoin-project/go-state-types/builtin" + datacap16 "github.com/filecoin-project/go-state-types/builtin/v16/datacap" + market16 "github.com/filecoin-project/go-state-types/builtin/v16/market" + miner16 "github.com/filecoin-project/go-state-types/builtin/v16/miner" + power16 "github.com/filecoin-project/go-state-types/builtin/v16/power" + "github.com/filecoin-project/go-state-types/builtin/v16/util/adt" + verifreg16 "github.com/filecoin-project/go-state-types/builtin/v16/verifreg" + + "github.com/filecoin-project/lotus/chain/types" +) + +// matchKnownBlockType attempts to determine the type of a block by inspecting its bytes. First we +// attempt to decode it as part of a HAMT or AMT, and if we get one, we inspect the types of the +// values. Otherwise we attempt to decode it as a known type using matchKnownBlockTypeFromBytes. +func matchKnownBlockType(ctx context.Context, nd blocks.Block) (string, error) { + if m, err := matchKnownBlockTypeFromBytes(nd.RawData()); err != nil { + return "", err + } else if m != "" { + return m, nil + } + + // block store with just one block in it, for interacting with the hamt and amt libraries + store := cbor.NewMemCborStore() + if err := store.(*cbor.BasicIpldStore).Blocks.Put(ctx, nd); err != nil { + return "", err + } + + // try to load as a HAMT root/node (they are the same thing) + if _, err := hamt.LoadNode(ctx, store, nd.Cid(), append(adt.DefaultHamtOptions, hamt.UseTreeBitWidth(gstbuiltin.DefaultHamtBitwidth))...); err == nil { + // got a HAMT, now inspect it + hamtNode, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("HamtNode"))) + if err != nil { + return "", xerrors.Errorf("failed to decode HamtNode: %w", err) + } + typ, err := matchHamtValues(hamtNode) + if err != nil { + return "", err + } + return fmt.Sprintf("HAMTNode{%d}%s", gstbuiltin.DefaultHamtBitwidth, typ), nil + } + + // try to load as an AMT root, we have to try all bitwidths used in the chain + for _, bitwidth := range []uint{2, 3, 4, 5, 6} { + if _, err := amt.LoadAMT(ctx, store, nd.Cid(), append(adt.DefaultAmtOptions, amt.UseTreeBitWidth(bitwidth))...); err == nil { + // got an AMT root, now inspect it + amtRoot, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("AMTRoot"))) + if err != nil { + return "", xerrors.Errorf("failed to decode AMTRoot: %w", err) + } + values, err := traversal.Get(amtRoot, datamodel.ParsePath("Node/Values")) + if err != nil { + return "", xerrors.Errorf("failed to get AMTRoot.Node.Values: %w", err) + } + typ, err := matchAmtValues(values) + if err != nil { + return "", err + } + return fmt.Sprintf("AMTRoot{%d}%s", bitwidth, typ), nil + } + } + + // try to load as an AMT intermediate node, which we can't do using the amt package so we'll + // infer by schema + if amtNode, err := ipld.DecodeUsingPrototype(nd.RawData(), dagcbor.Decode, bindnode.Prototype(nil, knownTypeSystem.TypeByName("AMTNode"))); err == nil { + // got an AMT node, now inspect it + values, err := amtNode.LookupByString("Values") + if err != nil { + return "", xerrors.Errorf("failed to get AMTNode.Values: %w", err) + } + typ, err := matchAmtValues(values) + if err != nil { + return "", err + } + return "AMTNode" + typ, nil + } + + return "", nil +} + +// given a datamodel.Node form of the Values array within an AMT node, attempt to determine the +// type of the values by iterating through them all and checking from their bytes. +func matchAmtValues(values datamodel.Node) (string, error) { + var match string + itr := values.ListIterator() + for !itr.Done() { + _, v, err := itr.Next() + if err != nil { + return "", err + } + enc, err := ipld.Encode(v, dagcbor.Encode) + if err != nil { + return "", err + } + if m, _ := matchKnownBlockTypeFromBytes(enc); m != "" { + if match == "" { + match = m + } else if match != m { + return "", xerrors.Errorf("inconsistent types in AMT values") + } + } + } + if match != "" { + return "[" + match + "]", nil + } + return "", nil +} + +// given a datamodel.Node form of a HAMT node, attempt to determine the type of the values, if there +// are any, by iterating through them all and checking from their bytes. +func matchHamtValues(hamtNode datamodel.Node) (string, error) { + pointers, err := hamtNode.LookupByString("Pointers") + if err != nil { + return "", xerrors.Errorf("failed to get HamtNode.Pointers: %w", err) + } + var match string + itr := pointers.ListIterator() + for !itr.Done() { + _, v, err := itr.Next() + if err != nil { + return "", err + } + b, err := v.LookupByString("Bucket") + if err == nil { + bitr := b.ListIterator() + for !bitr.Done() { + _, kv, err := bitr.Next() + if err != nil { + return "", err + } + bval, err := kv.LookupByString("Value") + if err != nil { + return "", err + } + enc, err := ipld.Encode(bval, dagcbor.Encode) + if err != nil { + return "", err + } + if m, _ := matchKnownBlockTypeFromBytes(enc); m != "" { + if match == "" { + match = m + } else if match != m { + return "", xerrors.Errorf("inconsistent types in HAMT values") + } + } + } + } + } + if match != "" { + return "[" + match + "]", nil + } + return "", nil +} + +var wellKnownBlockBytes = map[string][]byte{ + "EmptyArray": {0x80}, + "EmptyBytes": {0x40}, + "EmptyString": {0x60}, // is this used anywhere in the chain? + "Zero": {0x00}, // is this used anywhere in the chain? + "HAMTNode{5}[empty]": {0x82, 0x40, 0x80}, + "AMTRoot{2/3}[empty]": {0x84, 0x02, 0x00, 0x00, 0x83, 0x41, 0x00, 0x80, 0x80}, + "AMTRoot{4}[empty]": {0x84, 0x04, 0x00, 0x00, 0x83, 0x42, 0x00, 0x00, 0x80, 0x80}, + "AMTRoot{5}[empty]": {0x84, 0x05, 0x00, 0x00, 0x83, 0x44, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80}, + "AMTRoot{6}[empty]": {0x84, 0x06, 0x00, 0x00, 0x83, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80}, +} + +func matchWellKnownBlockType(b []byte) (string, error) { + for name, wkb := range wellKnownBlockBytes { + if bytes.Equal(b, wkb) { + return name, nil + } + } + return "", nil +} + +// matchKnownBlockTypeFromBytes attempts to determine the type of a block by inspecting its bytes. +// We use a fixed list of known types that have a CBORUnmarshaler that we believe may be possible. +// This list is not exhaustive and should be expanded as unknown types are encountered. +func matchKnownBlockTypeFromBytes(b []byte) (string, error) { + if m, _ := matchWellKnownBlockType(b); m != "" { + return m, nil + } + + if _, err := cbg.ReadCid(bytes.NewReader(b)); err == nil { + return "Cid", nil + } + known := map[string]cbg.CBORUnmarshaler{ + // Fill this out with known types when you see them missing and can identify them + "BlockHeader": &types.BlockHeader{}, + "miner16.State": &miner16.State{}, + "miner16.MinerInfo": &miner16.MinerInfo{}, + "miner16.Deadlines": &miner16.Deadlines{}, + "miner16.Deadline": &miner16.Deadline{}, + "miner16.Partition": &miner16.Partition{}, + "miner16.ExpirationSet": &miner16.ExpirationSet{}, + "miner16.WindowedPoSt": &miner16.WindowedPoSt{}, + "miner16.SectorOnChainInfo": &miner16.SectorOnChainInfo{}, + "miner16.SectorPreCommitOnChainInfo": &miner16.SectorPreCommitOnChainInfo{}, + "power16.State": &power16.State{}, + "market16.State": &market16.State{}, + "verifreg16.State": &verifreg16.State{}, + "datacap16.State": &datacap16.State{}, + "Bitfield": &bitfield.BitField{}, + } + for name, v := range known { + if err := v.UnmarshalCBOR(bytes.NewReader(b)); err == nil { + return name, nil + } + } + return "", nil +} + +const knownTypesSchema = ` +type HamtNode struct { + Bitfield Bytes + Pointers [Pointer] +} representation tuple + +type Pointer union { + | Any link # link to HamtNode + | Bucket list +} representation kinded + +type Bucket [KV] + +type KV struct { + Key Bytes + Value Any +} representation tuple + +type AMTNode struct { + Bmap Bytes + Links [Link] + Values [Any] +} representation tuple + +type AMTRoot struct { + BitWidth Int + Height Int + Count Int + Node AMTNode +} representation tuple +` + +var knownTypeSystem schema.TypeSystem + +func init() { + sch, err := schemadsl.ParseBytes([]byte(knownTypesSchema)) + if err != nil { + panic(err) + } + knownTypeSystem.Init() + if err := schemadmt.Compile(&knownTypeSystem, sch); err != nil { + panic(err) + } +} diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 0a8e77c185e..2c1f2932ddd 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -2,15 +2,16 @@ package main import ( "bytes" + "context" "encoding/base64" "encoding/hex" "encoding/json" - "errors" "fmt" "io" "os" "path" "sort" + "strings" "github.com/fatih/color" blocks "github.com/ipfs/go-block-format" @@ -23,6 +24,7 @@ import ( "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" @@ -112,28 +114,9 @@ var msgCmd = &cli.Command{ fmt.Println() if res.CachedBlocks != nil { - cachedBlocksFile := path.Join(os.TempDir(), msg.Cid().String()+".car") - if _, err := os.Stat(cachedBlocksFile); !errors.Is(err, os.ErrNotExist) { - return xerrors.Errorf("cached blocks file %s already exists: %w", cachedBlocksFile, err) - } - bs, err := carbstore.OpenReadWrite(cachedBlocksFile, nil, carbstore.WriteAsCarV1(true)) - if err != nil { - return xerrors.Errorf("opening cached blocks file: %w", err) - } - for _, b := range res.CachedBlocks { - bc, err := blocks.NewBlockWithCid(b.Data, b.Cid) - if err != nil { - return xerrors.Errorf("creating cached block: %w", err) - } - if err := bs.Put(ctx, bc); err != nil { - return xerrors.Errorf("writing cached block: %w", err) - } - } - if err := bs.Close(); err != nil { - return xerrors.Errorf("closing cached blocks file: %w", err) + if err := saveAndInspectBlocks(ctx, res, cctx.App.Writer); err != nil { + return err } - color.Green("Cached blocks written to %s", cachedBlocksFile) - fmt.Println() } color.Green("Receipt:") @@ -541,3 +524,114 @@ func gasTracesPerCall(inTrace types.ExecutionTrace) types.ExecutionTrace { accum(inTrace.Msg.To.String(), inTrace) return outTrace } + +func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writer) (err error) { + cachedBlocksFile := path.Join(os.TempDir(), res.MsgCid.String()+".car") + bs, err := carbstore.OpenReadWrite(cachedBlocksFile, nil, carbstore.WriteAsCarV1(true)) + if err != nil { + return xerrors.Errorf("opening cached blocks file: %w", err) + } + + defer func() { + if cerr := bs.Close(); cerr != nil { + err = xerrors.Errorf("closing cached blocks file: %w", cerr) + } + }() + + for _, b := range res.CachedBlocks { + bc, err := blocks.NewBlockWithCid(b.Data, b.Cid) + if err != nil { + return xerrors.Errorf("creating cached block: %w", err) + } + if err := bs.Put(ctx, bc); err != nil { + return xerrors.Errorf("writing cached block: %w", err) + } + } + color.Green("Cached blocks written to %s", cachedBlocksFile) + + type blkStat struct { + cid cid.Cid + knownType string + size int + estimatedGas int + } + + var explainBlocks func(descPfx string, trace types.ExecutionTrace) error + explainBlocks = func(descPfx string, trace types.ExecutionTrace) error { + typ := "Message" + if descPfx != "" { + typ = "Subcall" + } + + blkStats := make([]blkStat, 0, len(res.CachedBlocks)) + var totalBytes, totalGas int + + for _, ll := range trace.Logs { + if strings.HasPrefix(ll, "block_link(") { + c, err := cid.Parse(strings.TrimSuffix(strings.TrimPrefix(ll, "block_link("), ")")) + if err != nil { + return xerrors.Errorf("parsing block cid: %w", err) + } + blk, err := bs.Get(ctx, c) + if err != nil { + return xerrors.Errorf("getting block (%s) from cached blocks: %w", c, err) + } + m, err := matchKnownBlockType(ctx, blk) + if err != nil { + return xerrors.Errorf("matching block type: %w", err) + } + size := len(blk.RawData()) + gas := 172000 + 334000 + 3340*size + blkStats = append(blkStats, blkStat{cid: c, knownType: m, size: size, estimatedGas: gas}) + totalBytes += size + totalGas += gas + } + } + + if len(blkStats) == 0 { + return nil + } + + _, _ = fmt.Fprintln(out, color.New(color.Bold).Sprint(fmt.Sprintf("%s (%s%s) block writes:", typ, descPfx, trace.Msg.To))) + tw := tablewriter.New( + tablewriter.Col("CID"), + tablewriter.Col("Known Type"), + tablewriter.Col("Size", tablewriter.RightAlign()), + tablewriter.Col("S%", tablewriter.RightAlign()), + tablewriter.Col("Estimated Gas", tablewriter.RightAlign()), + tablewriter.Col("G%", tablewriter.RightAlign()), + ) + for _, bs := range blkStats { + tw.Write(map[string]interface{}{ + "CID": bs.cid, + "Known Type": bs.knownType, + "Size": bs.size, + "S%": fmt.Sprintf("%.2f", float64(bs.size)/float64(totalBytes)*100), + "Estimated Gas": bs.estimatedGas, + "G%": fmt.Sprintf("%.2f", float64(bs.estimatedGas)/float64(totalGas)*100), + }) + } + tw.Write(map[string]interface{}{ + "CID": "Total", + "Known Type": "", + "Size": totalBytes, + "S%": "100.00", + "Estimated Gas": totalGas, + "G%": "100.00", + }) + if err := tw.Flush(out, tablewriter.WithBorders()); err != nil { + return xerrors.Errorf("flushing table: %w", err) + } + + for _, subtrace := range trace.Subcalls { + if err := explainBlocks(descPfx+trace.Msg.To.String()+"➜", subtrace); err != nil { + return err + } + } + return nil + } + if err := explainBlocks("", res.ExecutionTrace); err != nil { + return xerrors.Errorf("explaining blocks: %w", err) + } + return +} From 10bb53da9666e47ff75a87977cb6e113b79d91ca Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 11 Jan 2025 20:57:22 +1100 Subject: [PATCH 3/8] fixup! feat(exec): dump intermediate cache blocks from FVM exec in StateReplay --- cmd/lotus-shed/block-matcher.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/cmd/lotus-shed/block-matcher.go b/cmd/lotus-shed/block-matcher.go index 1d8c86776b8..a7c2c54aab4 100644 --- a/cmd/lotus-shed/block-matcher.go +++ b/cmd/lotus-shed/block-matcher.go @@ -19,8 +19,8 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/go-amt-ipld/v4" - "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-hamt-ipld/v3" + "github.com/filecoin-project/go-state-types/abi" gstbuiltin "github.com/filecoin-project/go-state-types/builtin" datacap16 "github.com/filecoin-project/go-state-types/builtin/v16/datacap" market16 "github.com/filecoin-project/go-state-types/builtin/v16/market" @@ -120,6 +120,10 @@ func matchAmtValues(values datamodel.Node) (string, error) { } else if match != m { return "", xerrors.Errorf("inconsistent types in AMT values") } + // To debug unknown AMT types, uncomment this block: + // } else { + // enc, _ := ipld.Encode(v, dagjson.Encode) + // return "", xerrors.Errorf("unknown type in AMT values: %s", enc) } } if match != "" { @@ -162,8 +166,12 @@ func matchHamtValues(hamtNode datamodel.Node) (string, error) { if match == "" { match = m } else if match != m { - return "", xerrors.Errorf("inconsistent types in HAMT values") + return "", xerrors.Errorf("inconsistent types in HAMT values: %s != %s", match, m) } + // To debug unknown HAMT types, uncomment this block: + // } else { + // enc, _ := ipld.Encode(bval, dagjson.Encode) + // return "", xerrors.Errorf("unknown type in HAMT values: %s", enc) } } } @@ -202,10 +210,11 @@ func matchKnownBlockTypeFromBytes(b []byte) (string, error) { if m, _ := matchWellKnownBlockType(b); m != "" { return m, nil } - if _, err := cbg.ReadCid(bytes.NewReader(b)); err == nil { return "Cid", nil } + ci := cbg.CborInt(1) + cb := cbg.CborBool(true) known := map[string]cbg.CBORUnmarshaler{ // Fill this out with known types when you see them missing and can identify them "BlockHeader": &types.BlockHeader{}, @@ -220,9 +229,16 @@ func matchKnownBlockTypeFromBytes(b []byte) (string, error) { "miner16.SectorPreCommitOnChainInfo": &miner16.SectorPreCommitOnChainInfo{}, "power16.State": &power16.State{}, "market16.State": &market16.State{}, + "market16.DealProposal": &market16.DealProposal{}, + "market16.DealState": &market16.DealState{}, "verifreg16.State": &verifreg16.State{}, + "verifreg16.Allocation": &verifreg16.Allocation{}, + "verifreg16.Claim": &verifreg16.Claim{}, "datacap16.State": &datacap16.State{}, - "Bitfield": &bitfield.BitField{}, + "[Int]": &market16.SectorDealIDs{}, // verifreg16.RmDcProposalID is one of these too, as are probably others, we can't be certain, it would be context dependent + "Int": &ci, + "Bool": &cb, + "Bytes": &abi.CborBytes{}, // could be TokenAmount, BigInt, etc } for name, v := range known { if err := v.UnmarshalCBOR(bytes.NewReader(b)); err == nil { From 54e210e64059989d9e5a424d98ece6d564c651eb Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Tue, 14 Jan 2025 10:14:07 +1100 Subject: [PATCH 4/8] feat(stmgr): CallWithStore to collect new post-Call state --- chain/stmgr/call.go | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 7fee85a8b41..e9276be978b 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -39,6 +39,14 @@ var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at e // tipset's parent. In the presence of null blocks, the height at which the message is invoked may // be less than the specified tipset. func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + return sm.CallWithStore(ctx, msg, ts, buffStore) +} + +// CallWithStore applies the given message to the given tipset's parent state, at the epoch following the +// tipset's parent. In the presence of null blocks, the height at which the message is invoked may +// be less than the specified tipset. +func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, ts *types.TipSet, bstore blockstore.Blockstore) (*api.InvocResult, error) { // Copy the message as we modify it below. msgCopy := *msg msg = &msgCopy @@ -55,13 +63,13 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types. if msg.Value == types.EmptyInt { msg.Value = types.NewInt(0) } - - return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages) + return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages, bstore) } // ApplyOnStateWithGas applies the given message on top of the given state root with gas tracing enabled func (sm *StateManager) ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { - return sm.callInternal(ctx, msg, nil, ts, stateCid, sm.GetNetworkVersion, true, execNoMessages) + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + return sm.callInternal(ctx, msg, nil, ts, stateCid, sm.GetNetworkVersion, true, execNoMessages, buffStore) } // CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state. @@ -72,8 +80,8 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri } else { strategy = execSameSenderMessages } - - return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, strategy) + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, strategy, buffStore) } // CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version. @@ -84,14 +92,24 @@ func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Me nvGetter := func(context.Context, abi.ChainEpoch) network.Version { return v } - return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages) + buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages, buffStore) } // - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used. // - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will // fail with ErrExpensiveFork. -func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, stateCid cid.Cid, - nvGetter rand.NetworkVersionGetter, checkGas bool, strategy execMessageStrategy) (*api.InvocResult, error) { +func (sm *StateManager) callInternal( + ctx context.Context, + msg *types.Message, + priorMsgs []types.ChainMsg, + ts *types.TipSet, + stateCid cid.Cid, + nvGetter rand.NetworkVersionGetter, + checkGas bool, + strategy execMessageStrategy, + bstore blockstore.Blockstore, +) (*api.InvocResult, error) { ctx, span := trace.StartSpan(ctx, "statemanager.callInternal") defer span.End() @@ -151,13 +169,12 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr ) } - buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) vmopt := &vm.VMOpts{ StateBase: stateCid, Epoch: ts.Height(), Timestamp: ts.MinTimestamp(), Rand: rand.NewStateRand(sm.cs, ts.Cids(), sm.beacon, nvGetter), - Bstore: buffStore, + Bstore: bstore, Actors: sm.tsExec.NewActorRegistry(), Syscalls: sm.Syscalls, CircSupplyCalc: sm.GetVMCirculatingSupply, @@ -207,7 +224,7 @@ func (sm *StateManager) callInternal(ctx context.Context, msg *types.Message, pr } } - stTree, err := state.LoadStateTree(cbor.NewCborStore(buffStore), stateCid) + stTree, err := state.LoadStateTree(cbor.NewCborStore(bstore), stateCid) if err != nil { return nil, xerrors.Errorf("loading state tree: %w", err) } From 17543c9fde8b8248d4701a89ca7d4b7922920a7a Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 27 Mar 2025 19:24:08 +1100 Subject: [PATCH 5/8] s/DumpCache/FlushAllBlocks option --- api/api_full.go | 69 +++++++++++++++++++++++++++++++- chain/consensus/compute_state.go | 4 ++ chain/stmgr/call.go | 16 ++++---- chain/vm/execution.go | 5 --- chain/vm/fvm.go | 8 ---- chain/vm/vm.go | 5 +++ chain/vm/vmi.go | 4 -- extern/filecoin-ffi | 2 +- 8 files changed, 86 insertions(+), 27 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index 4b2f0675c46..9ddb3c6ac21 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -8,6 +8,7 @@ import ( blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" @@ -385,7 +386,7 @@ type FullNode interface { // StateCall applies the message to the tipset's parent state. The // message is not applied on-top-of the messages in the passed-in // tipset. - StateCall(context.Context, *types.Message, types.TipSetKey) (*InvocResult, error) //perm:read + StateCall(ctx context.Context, p StateCallParams) (*InvocResult, error) //perm:read // StateReplay replays a given message, assuming it was included in a block in the specified tipset. // // If a tipset key is provided, and a replacing message is not found on chain, @@ -1270,7 +1271,12 @@ type InvocResult struct { ExecutionTrace types.ExecutionTrace Error string Duration time.Duration - CachedBlocks []Block `json:",omitempty"` + Blocks *InvocBlocks `json:",omitempty"` +} + +type InvocBlocks struct { + Root cid.Cid + Blocks []Block } type Block struct { @@ -1475,3 +1481,62 @@ type EthTxReceipt struct { Logs []ethtypes.EthLog `json:"logs"` Type ethtypes.EthUint64 `json:"type"` } + +type StateCallParams struct { + Message *types.Message `json:"message"` + TipSetKey types.TipSetKey `json:"tipsetKey"` + IncludeBlocks bool `json:"includeBlocks,omitempty"` +} + +func (e *StateCallParams) ToArg() jsonrpc.RawParams { + b, err := json.Marshal(e) + if err != nil { + return nil + } + return jsonrpc.RawParams(b) +} + +func (e *StateCallParams) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return xerrors.New("empty input") + } + + // If input is an object, decode it directly into a temporary struct + if b[0] == '{' { + type stateCallParamsAlias StateCallParams + var alias stateCallParamsAlias + if err := json.Unmarshal(b, &alias); err != nil { + return err + } + *e = StateCallParams(alias) + return nil + } + + // If input is an array, expect exactly two elements: Message and TipSetKey + if b[0] == '[' { + var params []json.RawMessage + if err := json.Unmarshal(b, ¶ms); err != nil { + return err + } + if len(params) != 2 { + return xerrors.Errorf("expected two parameters, got %d", len(params)) + } + if err := json.Unmarshal(params[0], &e.Message); err != nil { + return xerrors.Errorf("failed to unmarshal message: %w", err) + } + if err := json.Unmarshal(params[1], &e.TipSetKey); err != nil { + return xerrors.Errorf("failed to unmarshal tipset key: %w", err) + } + return nil + } + + return xerrors.Errorf("unexpected JSON input: expected object or array, got %q", string(b[0])) +} + +func (e *StateCallParams) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "message": e.Message, + "tipsetKey": e.TipSetKey, + "includeBlocks": e.IncludeBlocks, + }) +} diff --git a/chain/consensus/compute_state.go b/chain/consensus/compute_state.go index 46576b47f0b..6727e9c8c1a 100644 --- a/chain/consensus/compute_state.go +++ b/chain/consensus/compute_state.go @@ -245,10 +245,12 @@ func (t *TipSetExecutor) ApplyBlocks( if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { log.Debugw("ApplyBlocks ExecMonitor#MessageApplied callback failed", "error", err) if cacheStore != nil { + /* TODO: log.Debug("Dumping vm cache blocks to provided cacheStore") if err := vmi.DumpCache(cacheStore); err != nil { return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) } + */ } return cid.Undef, cid.Undef, err } @@ -307,10 +309,12 @@ func (t *TipSetExecutor) ApplyBlocks( } if cacheStore != nil { + /* TODO: log.Debug("Dumping vm cache blocks to provided cacheStore") if err := vmi.DumpCache(cacheStore); err != nil { return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) } + */ } st, err := vmi.Flush(ctx) diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index e9276be978b..508af5ea078 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -38,15 +38,15 @@ var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at e // Call applies the given message to the given tipset's parent state, at the epoch following the // tipset's parent. In the presence of null blocks, the height at which the message is invoked may // be less than the specified tipset. -func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { +func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) { buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - return sm.CallWithStore(ctx, msg, ts, buffStore) + return sm.CallWithStore(ctx, msg, ts, buffStore, flushAllBlocks) } // CallWithStore applies the given message to the given tipset's parent state, at the epoch following the // tipset's parent. In the presence of null blocks, the height at which the message is invoked may // be less than the specified tipset. -func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, ts *types.TipSet, bstore blockstore.Blockstore) (*api.InvocResult, error) { +func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, ts *types.TipSet, bstore blockstore.Blockstore, flushAllBlocks bool) (*api.InvocResult, error) { // Copy the message as we modify it below. msgCopy := *msg msg = &msgCopy @@ -63,13 +63,13 @@ func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, t if msg.Value == types.EmptyInt { msg.Value = types.NewInt(0) } - return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages, bstore) + return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages, bstore, flushAllBlocks) } // ApplyOnStateWithGas applies the given message on top of the given state root with gas tracing enabled func (sm *StateManager) ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - return sm.callInternal(ctx, msg, nil, ts, stateCid, sm.GetNetworkVersion, true, execNoMessages, buffStore) + return sm.callInternal(ctx, msg, nil, ts, stateCid, sm.GetNetworkVersion, true, execNoMessages, buffStore, false) } // CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state. @@ -81,7 +81,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri strategy = execSameSenderMessages } buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, strategy, buffStore) + return sm.callInternal(ctx, msg, priorMsgs, ts, cid.Undef, sm.GetNetworkVersion, true, strategy, buffStore, false) } // CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version. @@ -93,7 +93,7 @@ func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Me return v } buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages, buffStore) + return sm.callInternal(ctx, msg, nil, nil, stateCid, nvGetter, true, execSameSenderMessages, buffStore, false) } // - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used. @@ -109,6 +109,7 @@ func (sm *StateManager) callInternal( checkGas bool, strategy execMessageStrategy, bstore blockstore.Blockstore, + flushAllBlocks bool, ) (*api.InvocResult, error) { ctx, span := trace.StartSpan(ctx, "statemanager.callInternal") defer span.End() @@ -183,6 +184,7 @@ func (sm *StateManager) callInternal( LookbackState: LookbackStateGetterForTipset(sm, ts), TipSetGetter: TipSetGetterForTipset(sm.cs, ts), Tracing: true, + FlushAllBlocks: flushAllBlocks, } vmi, err := sm.newVM(ctx, vmopt) if err != nil { diff --git a/chain/vm/execution.go b/chain/vm/execution.go index fce917427a1..4fb626f4390 100644 --- a/chain/vm/execution.go +++ b/chain/vm/execution.go @@ -10,7 +10,6 @@ import ( "go.opencensus.io/stats" "go.opencensus.io/tag" - "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/metrics" ) @@ -59,10 +58,6 @@ func (e *vmExecutor) Flush(ctx context.Context) (cid.Cid, error) { return e.vmi.Flush(ctx) } -func (e *vmExecutor) DumpCache(bs blockstore.Blockstore) error { - return e.vmi.DumpCache(bs) -} - type executionToken struct { lane ExecutionLane reserved int diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index 6190dbbaa00..77103d31d8d 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -538,10 +538,6 @@ func (vm *FVM) Flush(ctx context.Context) (cid.Cid, error) { return vm.fvm.Flush() } -func (vm *FVM) DumpCache(cacheStore blockstore.Blockstore) error { - return vm.fvm.DumpCache(cacheStore) -} - type dualExecutionFVM struct { main *FVM debug *FVM @@ -612,10 +608,6 @@ func (vm *dualExecutionFVM) Flush(ctx context.Context) (cid.Cid, error) { return vm.main.Flush(ctx) } -func (vm *dualExecutionFVM) DumpCache(cacheStore blockstore.Blockstore) error { - return vm.main.DumpCache(cacheStore) -} - // Passing this as a pointer of structs has proven to be an enormous PiTA; hence this code. type xRedirect struct{ from, to cid.Cid } type xMapping struct{ redirects []xRedirect } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index a9ba781dd3f..8b5283d0b1c 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -249,6 +249,11 @@ type VMOpts struct { LookbackState LookbackStateGetter TipSetGetter TipSetGetter Tracing bool + // FlushAllBlocks is used to control whether the VM should flush all blocks + // created during execution to the blockstore. This is used for testing + // purposes, where we want to inspect all blocks created during execution, not + // just those connected to the final state root. + FlushAllBlocks bool // ReturnEvents decodes and returns emitted events. ReturnEvents bool // ExecutionLane specifies the execution priority of the created vm diff --git a/chain/vm/vmi.go b/chain/vm/vmi.go index 56ba6f1a5bc..042621ca2d4 100644 --- a/chain/vm/vmi.go +++ b/chain/vm/vmi.go @@ -9,7 +9,6 @@ import ( "github.com/filecoin-project/go-state-types/network" - bstore "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/chain/types" ) @@ -36,9 +35,6 @@ type Interface interface { ApplyImplicitMessage(ctx context.Context, msg *types.Message) (*ApplyRet, error) // Flush all buffered objects into the state store provided to the VM at construction. Flush(ctx context.Context) (cid.Cid, error) - // Dump the contents of the caching blockstore to the provided blockstore. This will include the - // final state tree as well as any intermediate objects created during messagae execution. - DumpCache(bs bstore.Blockstore) error } // WARNING: You will not affect your node's execution by misusing this feature, but you will confuse yourself thoroughly! diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 2e0c1aff700..037aa5ce119 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 2e0c1aff7003f3ca8df46396ad87d275e9517e45 +Subproject commit 037aa5ce11958eb2b47799b4cf16922874f6e2e1 From 5113d89cae2b775b5dc3841a6f2d75005397968e Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 28 Mar 2025 17:29:30 +1100 Subject: [PATCH 6/8] Plumb StateCall's flushAllBlocks and write into result --- api/api_full.go | 62 ++++++++++++++----- api/api_gateway.go | 2 +- api/mocks/mock_full.go | 4 +- api/proxy_gen.go | 16 ++--- api/v0api/full.go | 3 +- api/v0api/gateway.go | 3 +- api/v0api/proxy_gen.go | 17 ++--- api/v0api/v0mocks/mock_full.go | 9 +-- blockstore/accumulator.go | 86 ++++++++++++++++++++++++++ chain/consensus/compute_state.go | 21 +------ chain/lf3/manifest.go | 6 +- chain/stmgr/call.go | 73 ++++++++++++++++------ chain/stmgr/execute.go | 4 +- chain/stmgr/forks_test.go | 4 +- chain/stmgr/rpc/rpcstatemanager.go | 2 +- chain/stmgr/stmgr.go | 5 +- cli/disputer.go | 4 +- cli/state.go | 4 +- cmd/lotus-shed/f3.go | 15 ++--- cmd/lotus-shed/main.go | 1 + cmd/lotus-shed/msg.go | 55 ++++++++-------- cmd/lotus-shed/sectors.go | 3 +- cmd/tvx/state.go | 2 +- conformance/driver.go | 1 - gateway/node.go | 2 +- gateway/proxy_fil.go | 11 +++- itests/remove_verifreg_datacap_test.go | 4 +- itests/wdpost_test.go | 2 +- node/impl/eth/api.go | 2 +- node/impl/eth/lookup.go | 4 +- node/impl/full/state.go | 57 +++++------------ paychmgr/manager.go | 2 +- paychmgr/mock_test.go | 2 +- paychmgr/paych.go | 2 +- 34 files changed, 300 insertions(+), 190 deletions(-) create mode 100644 blockstore/accumulator.go diff --git a/api/api_full.go b/api/api_full.go index 9ddb3c6ac21..4a7958c2894 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -386,7 +386,7 @@ type FullNode interface { // StateCall applies the message to the tipset's parent state. The // message is not applied on-top-of the messages in the passed-in // tipset. - StateCall(ctx context.Context, p StateCallParams) (*InvocResult, error) //perm:read + StateCall(ctx context.Context, p jsonrpc.RawParams) (*InvocResult, error) //perm:read // StateReplay replays a given message, assuming it was included in a block in the specified tipset. // // If a tipset key is provided, and a replacing message is not found on chain, @@ -1271,12 +1271,7 @@ type InvocResult struct { ExecutionTrace types.ExecutionTrace Error string Duration time.Duration - Blocks *InvocBlocks `json:",omitempty"` -} - -type InvocBlocks struct { - Root cid.Cid - Blocks []Block + Blocks []Block `json:",omitempty"` } type Block struct { @@ -1482,13 +1477,39 @@ type EthTxReceipt struct { Type ethtypes.EthUint64 `json:"type"` } +// StateCallParams is the parameters for the StateCall method. type StateCallParams struct { Message *types.Message `json:"message"` TipSetKey types.TipSetKey `json:"tipsetKey"` IncludeBlocks bool `json:"includeBlocks,omitempty"` } -func (e *StateCallParams) ToArg() jsonrpc.RawParams { +// NewStateCallParams creates a new StateCallParams instance with the given message and tipset key. +func NewStateCallParams(msg *types.Message, tsk types.TipSetKey) StateCallParams { + return StateCallParams{ + Message: msg, + TipSetKey: tsk, + IncludeBlocks: false, + } +} + +// StateCallParamsFromRaw converts raw jsonrpc parameters to StateCallParams. +func StateCallParamsFromRaw(raw jsonrpc.RawParams) (StateCallParams, error) { + return jsonrpc.DecodeParams[StateCallParams](raw) +} + +// ToRaw converts the StateCallParams to a jsonrpc.RawParams format for use with the StateCall +// method. +func (e StateCallParams) ToRaw() jsonrpc.RawParams { + if !e.IncludeBlocks { + // if we have 2 arguments, encode using the array format for backward compatibility with the + // old API, if a new client uses this to call an old server, it will be interpreted correctly + // as just 2 arguments + if b, err := json.Marshal([]any{e.Message, e.TipSetKey}); err == nil { + return jsonrpc.RawParams(b) + } // else fall through to the object format, maybe it'll work + } + // IncludeBlocks forces us to use the object format, so we can't be compatible with the old API b, err := json.Marshal(e) if err != nil { return nil @@ -1496,6 +1517,15 @@ func (e *StateCallParams) ToArg() jsonrpc.RawParams { return jsonrpc.RawParams(b) } +// WithIncludeBlocks returns a new StateCallParams with IncludeBlocks set to true. +func (e StateCallParams) WithIncludeBlocks() StateCallParams { + return StateCallParams{ + Message: e.Message, + TipSetKey: e.TipSetKey, + IncludeBlocks: true, + } +} + func (e *StateCallParams) UnmarshalJSON(b []byte) error { if len(b) == 0 { return xerrors.New("empty input") @@ -1509,11 +1539,8 @@ func (e *StateCallParams) UnmarshalJSON(b []byte) error { return err } *e = StateCallParams(alias) - return nil - } - - // If input is an array, expect exactly two elements: Message and TipSetKey - if b[0] == '[' { + // If input is an array, expect exactly two elements: Message and TipSetKey + } else if b[0] == '[' { var params []json.RawMessage if err := json.Unmarshal(b, ¶ms); err != nil { return err @@ -1527,10 +1554,15 @@ func (e *StateCallParams) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(params[1], &e.TipSetKey); err != nil { return xerrors.Errorf("failed to unmarshal tipset key: %w", err) } - return nil + } else { + return xerrors.Errorf("unexpected JSON input: expected object or array, got %q", string(b[0])) + } + + if e.Message == nil { + return xerrors.New("message required") } - return xerrors.Errorf("unexpected JSON input: expected object or array, got %q", string(b[0])) + return nil } func (e *StateCallParams) MarshalJSON() ([]byte, error) { diff --git a/api/api_gateway.go b/api/api_gateway.go index 6e39bd10f01..c092181b6f9 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -64,7 +64,7 @@ type Gateway interface { MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) MsigGetVestingSchedule(ctx context.Context, addr address.Address, tsk types.TipSetKey) (MsigVesting, error) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) - StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*InvocResult, error) + StateCall(ctx context.Context, p jsonrpc.RawParams) (*InvocResult, error) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (DealCollateralBounds, error) StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 725b3d08756..3d5d52a1871 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -2868,9 +2868,9 @@ func (mr *MockFullNodeMockRecorder) StateAllMinerFaults(arg0, arg1, arg2 interfa } // StateCall mocks base method. -func (m *MockFullNode) StateCall(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (*api.InvocResult, error) { +func (m *MockFullNode) StateCall(arg0 context.Context, arg1 jsonrpc.RawParams) (*api.InvocResult, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StateCall", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "StateCall", arg0, arg1) ret0, _ := ret[0].(*api.InvocResult) ret1, _ := ret[1].(error) return ret0, ret1 diff --git a/api/proxy_gen.go b/api/proxy_gen.go index d4f82b4bc81..c331af05d62 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -422,7 +422,7 @@ type FullNodeMethods struct { StateAllMinerFaults func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*Fault, error) `perm:"read"` - StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) `perm:"read"` + StateCall func(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) `perm:"read"` StateChangedActors func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) `perm:"read"` @@ -764,7 +764,7 @@ type GatewayMethods struct { StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` - StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) `` + StateCall func(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) `` StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (DealCollateralBounds, error) `` @@ -3053,14 +3053,14 @@ func (s *FullNodeStub) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch return *new([]*Fault), ErrNotSupported } -func (s *FullNodeStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { +func (s *FullNodeStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) { if s.Internal.StateCall == nil { return nil, ErrNotSupported } - return s.Internal.StateCall(p0, p1, p2) + return s.Internal.StateCall(p0, p1) } -func (s *FullNodeStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { +func (s *FullNodeStub) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) { return nil, ErrNotSupported } @@ -4868,14 +4868,14 @@ func (s *GatewayStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 return *new(address.Address), ErrNotSupported } -func (s *GatewayStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { +func (s *GatewayStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) { if s.Internal.StateCall == nil { return nil, ErrNotSupported } - return s.Internal.StateCall(p0, p1, p2) + return s.Internal.StateCall(p0, p1) } -func (s *GatewayStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) { +func (s *GatewayStub) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) { return nil, ErrNotSupported } diff --git a/api/v0api/full.go b/api/v0api/full.go index b1e196211d4..72f3499c10f 100644 --- a/api/v0api/full.go +++ b/api/v0api/full.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin/v8/paych" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" @@ -308,7 +309,7 @@ type FullNode interface { // StateCall applies the message to the tipset's parent state. The // message is not applied on-top-of the messages in the passed-in // tipset. - StateCall(context.Context, *types.Message, types.TipSetKey) (*api.InvocResult, error) //perm:read + StateCall(context.Context, jsonrpc.RawParams) (*api.InvocResult, error) //perm:read // StateReplay replays a given message, assuming it was included in a block in the specified tipset. // // If a tipset key is provided, and a replacing message is not found on chain, diff --git a/api/v0api/gateway.go b/api/v0api/gateway.go index 1a7f7d3ac92..055fc807abd 100644 --- a/api/v0api/gateway.go +++ b/api/v0api/gateway.go @@ -7,6 +7,7 @@ import ( "github.com/ipfs/go-cid" "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" "github.com/filecoin-project/go-state-types/dline" @@ -58,7 +59,7 @@ type Gateway interface { MsigGetVested(ctx context.Context, addr address.Address, start types.TipSetKey, end types.TipSetKey) (types.BigInt, error) MsigGetPending(context.Context, address.Address, types.TipSetKey) ([]*api.MsigTransaction, error) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) - StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) + StateCall(ctx context.Context, p jsonrpc.RawParams) (*api.InvocResult, error) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index a8756894951..9bcbf93e4ea 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -11,6 +11,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/builtin/v8/paych" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" @@ -206,7 +207,7 @@ type FullNodeMethods struct { StateAllMinerFaults func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) ([]*api.Fault, error) `perm:"read"` - StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) `perm:"read"` + StateCall func(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) `perm:"read"` StateChangedActors func(p0 context.Context, p1 cid.Cid, p2 cid.Cid) (map[string]types.Actor, error) `perm:"read"` @@ -414,7 +415,7 @@ type GatewayMethods struct { StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` - StateCall func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) `` + StateCall func(p0 context.Context, p jsonrpc.RawParams) (*api.InvocResult, error) `` StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) `` @@ -1407,14 +1408,14 @@ func (s *FullNodeStub) StateAllMinerFaults(p0 context.Context, p1 abi.ChainEpoch return *new([]*api.Fault), ErrNotSupported } -func (s *FullNodeStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { +func (s *FullNodeStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) { if s.Internal.StateCall == nil { return nil, ErrNotSupported } - return s.Internal.StateCall(p0, p1, p2) + return s.Internal.StateCall(p0, p1) } -func (s *FullNodeStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { +func (s *FullNodeStub) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) { return nil, ErrNotSupported } @@ -2485,14 +2486,14 @@ func (s *GatewayStub) StateAccountKey(p0 context.Context, p1 address.Address, p2 return *new(address.Address), ErrNotSupported } -func (s *GatewayStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { +func (s *GatewayStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) { if s.Internal.StateCall == nil { return nil, ErrNotSupported } - return s.Internal.StateCall(p0, p1, p2) + return s.Internal.StateCall(p0, p1) } -func (s *GatewayStub) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*api.InvocResult, error) { +func (s *GatewayStub) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) { return nil, ErrNotSupported } diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index b2a4125675a..34a761dca1f 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -1,7 +1,3 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: github.com/filecoin-project/lotus/api/v0api (interfaces: FullNode) - -// Package v0mocks is a generated GoMock package. package v0mocks import ( @@ -20,6 +16,7 @@ import ( address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-jsonrpc" auth "github.com/filecoin-project/go-jsonrpc/auth" abi "github.com/filecoin-project/go-state-types/abi" big "github.com/filecoin-project/go-state-types/big" @@ -1831,9 +1828,9 @@ func (mr *MockFullNodeMockRecorder) StateAllMinerFaults(arg0, arg1, arg2 interfa } // StateCall mocks base method. -func (m *MockFullNode) StateCall(arg0 context.Context, arg1 *types.Message, arg2 types.TipSetKey) (*api.InvocResult, error) { +func (m *MockFullNode) StateCall(arg0 context.Context, arg1 jsonrpc.RawParams) (*api.InvocResult, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "StateCall", arg0, arg1, arg2) + ret := m.ctrl.Call(m, "StateCall", arg0, arg1) ret0, _ := ret[0].(*api.InvocResult) ret1, _ := ret[1].(error) return ret0, ret1 diff --git a/blockstore/accumulator.go b/blockstore/accumulator.go new file mode 100644 index 00000000000..375a9174fc0 --- /dev/null +++ b/blockstore/accumulator.go @@ -0,0 +1,86 @@ +package blockstore + +import ( + "context" + "sync" + + block "github.com/ipfs/go-block-format" + "github.com/ipfs/go-cid" +) + +// Accumulator is a type of blockstore that accumulates blocks in memory as they +// are written. An Accumulator otherwise acts as a pass-through to the +// underlying blockstore. +// +// Written blocks can be retrieved later using the GetBlocks method. This is +// useful for testing and debugging purposes. Care should be taken when using +// this feature for long-running applications where many blocks are written as +// there is no automatic eviction mechanism. ClearBlocks can be used to +// manually clear the accumulated blocks. +type Accumulator struct { + Blockstore + + cids []cid.Cid + lk sync.Mutex +} + +var ( + _ Blockstore = (*Accumulator)(nil) + _ Viewer = (*Accumulator)(nil) +) + +// NewAccumulator creates a new Accumulator blockstore that wraps the given +// Blockstore. The Accumulator will accumulate blocks in memory as they are +// written. The blocks can be retrieved later using the GetBlocks method. +func NewAccumulator(bs Blockstore) *Accumulator { + return &Accumulator{ + Blockstore: bs, + cids: make([]cid.Cid, 0), + } +} + +func (acc *Accumulator) Put(ctx context.Context, b block.Block) error { + acc.lk.Lock() + acc.cids = append(acc.cids, b.Cid()) + acc.lk.Unlock() + return acc.Blockstore.Put(ctx, b) +} + +func (acc *Accumulator) PutMany(ctx context.Context, blks []block.Block) error { + acc.lk.Lock() + for _, b := range blks { + acc.cids = append(acc.cids, b.Cid()) + } + acc.lk.Unlock() + return acc.Blockstore.PutMany(ctx, blks) +} + +// GetBlocks returns the blocks that have been put into the accumulator. +// It is safe to call this method concurrently with Put and PutMany. +// The returned slice is the internal slice of blocks, and should be handled +// with care if use of the blockstore is ongoing after this call. +func (acc *Accumulator) GetBlocks(ctx context.Context) ([]block.Block, error) { + acc.lk.Lock() + defer acc.lk.Unlock() + + blocks := make([]block.Block, len(acc.cids)) + for i, c := range acc.cids { + b, err := acc.Blockstore.Get(ctx, c) + if err != nil { + return nil, err + } + blocks[i] = b + } + return blocks, nil +} + +// ClearBlocks clears the blocks that have been accumulated in the +// Accumulator. This is useful for testing and debugging purposes. +// It is safe to call this method concurrently with Put and PutMany. +// The blocks are not removed from the underlying blockstore. +func (acc *Accumulator) ClearBlocks() { + acc.lk.Lock() + defer acc.lk.Unlock() + + acc.cids = acc.cids[:0] +} diff --git a/chain/consensus/compute_state.go b/chain/consensus/compute_state.go index 6727e9c8c1a..541e703d096 100644 --- a/chain/consensus/compute_state.go +++ b/chain/consensus/compute_state.go @@ -89,7 +89,6 @@ func (t *TipSetExecutor) ApplyBlocks( r rand.Rand, em stmgr.ExecMonitor, vmTracing bool, - cacheStore blockstore.Blockstore, baseFee abi.TokenAmount, ts *types.TipSet, ) (cid.Cid, cid.Cid, error) { @@ -244,14 +243,6 @@ func (t *TipSetExecutor) ApplyBlocks( if em != nil { if err := em.MessageApplied(ctx, ts, cm.Cid(), m, r, false); err != nil { log.Debugw("ApplyBlocks ExecMonitor#MessageApplied callback failed", "error", err) - if cacheStore != nil { - /* TODO: - log.Debug("Dumping vm cache blocks to provided cacheStore") - if err := vmi.DumpCache(cacheStore); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) - } - */ - } return cid.Undef, cid.Undef, err } } @@ -308,15 +299,6 @@ func (t *TipSetExecutor) ApplyBlocks( } } - if cacheStore != nil { - /* TODO: - log.Debug("Dumping vm cache blocks to provided cacheStore") - if err := vmi.DumpCache(cacheStore); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("dumping vm cache: %w", err) - } - */ - } - st, err := vmi.Flush(ctx) if err != nil { return cid.Undef, cid.Undef, xerrors.Errorf("vm flush failed: %w", err) @@ -338,7 +320,6 @@ func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, ts *types.TipSet, em stmgr.ExecMonitor, vmTracing bool, - cacheStore blockstore.Blockstore, ) (stateroot cid.Cid, rectsroot cid.Cid, err error) { ctx, span := trace.StartSpan(ctx, "computeTipSetState") defer span.End() @@ -387,7 +368,7 @@ func (t *TipSetExecutor) ExecuteTipSet(ctx context.Context, } baseFee := blks[0].ParentBaseFee - return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, vmTracing, cacheStore, baseFee, ts) + return t.ApplyBlocks(ctx, sm, parentEpoch, pstate, fbmsgs, blks[0].Height, r, em, vmTracing, baseFee, ts) } func (t *TipSetExecutor) StoreEventsAMT(ctx context.Context, cs *store.ChainStore, events []types.Event) (cid.Cid, error) { diff --git a/chain/lf3/manifest.go b/chain/lf3/manifest.go index 1da82c8efbc..478441fca06 100644 --- a/chain/lf3/manifest.go +++ b/chain/lf3/manifest.go @@ -21,13 +21,13 @@ import ( "github.com/filecoin-project/go-f3/ec" "github.com/filecoin-project/go-f3/gpbft" "github.com/filecoin-project/go-f3/manifest" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/store" - "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" "github.com/filecoin-project/lotus/lib/must" "github.com/filecoin-project/lotus/node/modules/dtypes" @@ -129,7 +129,7 @@ func NewManifestProvider(mctx helpers.MetricsCtx, config *Config, cs *store.Chai } type StateCaller interface { - StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) + StateCall(ctx context.Context, p jsonrpc.RawParams) (res *api.InvocResult, err error) } type ContractManifestProvider struct { @@ -330,7 +330,7 @@ func (cmp *ContractManifestProvider) callContract(ctx context.Context) ([]byte, return nil, fmt.Errorf("converting to filecoin message: %w", err) } - msgRes, err := cmp.stateCaller.StateCall(ctx, fMessage, types.EmptyTSK) + msgRes, err := cmp.stateCaller.StateCall(ctx, api.StateCallParams{Message: fMessage}.ToRaw()) if err != nil { return nil, fmt.Errorf("state call error: %w", err) } diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 508af5ea078..40939752001 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -38,15 +38,11 @@ var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at e // Call applies the given message to the given tipset's parent state, at the epoch following the // tipset's parent. In the presence of null blocks, the height at which the message is invoked may // be less than the specified tipset. +// +// If flushAllBlocks is set, the blocks written during the execution of the message will be returned +// in the result's Blocks field. This includes all blocks, including intermediate state that may not +// be part of the final state root. func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) { - buffStore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) - return sm.CallWithStore(ctx, msg, ts, buffStore, flushAllBlocks) -} - -// CallWithStore applies the given message to the given tipset's parent state, at the epoch following the -// tipset's parent. In the presence of null blocks, the height at which the message is invoked may -// be less than the specified tipset. -func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, ts *types.TipSet, bstore blockstore.Blockstore, flushAllBlocks bool) (*api.InvocResult, error) { // Copy the message as we modify it below. msgCopy := *msg msg = &msgCopy @@ -63,6 +59,9 @@ func (sm *StateManager) CallWithStore(ctx context.Context, msg *types.Message, t if msg.Value == types.EmptyInt { msg.Value = types.NewInt(0) } + + bstore := blockstore.NewTieredBstore(sm.cs.StateBlockstore(), blockstore.NewMemorySync()) + return sm.callInternal(ctx, msg, nil, ts, cid.Undef, sm.GetNetworkVersion, false, execSameSenderMessages, bstore, flushAllBlocks) } @@ -99,6 +98,7 @@ func (sm *StateManager) CallAtStateAndVersion(ctx context.Context, msg *types.Me // - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used. // - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will // fail with ErrExpensiveFork. +// - If flushAllBlocks is true, written blocks will be tracked and added to the result's Blocks field. func (sm *StateManager) callInternal( ctx context.Context, msg *types.Message, @@ -111,6 +111,7 @@ func (sm *StateManager) callInternal( bstore blockstore.Blockstore, flushAllBlocks bool, ) (*api.InvocResult, error) { + ctx, span := trace.StartSpan(ctx, "statemanager.callInternal") defer span.End() @@ -184,7 +185,7 @@ func (sm *StateManager) callInternal( LookbackState: LookbackStateGetterForTipset(sm, ts), TipSetGetter: TipSetGetterForTipset(sm.cs, ts), Tracing: true, - FlushAllBlocks: flushAllBlocks, + FlushAllBlocks: false, } vmi, err := sm.newVM(ctx, vmopt) if err != nil { @@ -202,14 +203,17 @@ func (sm *StateManager) callInternal( if strategy == execAllMessages { priorMsgs = append(tsMsgs, priorMsgs...) } else if strategy == execSameSenderMessages { - var filteredTsMsgs []types.ChainMsg for _, tsMsg := range tsMsgs { + if tsMsg.VMMessage().Cid() == msg.VMMessage().Cid() { + // The message we've been asked to execute is in here, we don't need to add it to priorMsgs + // and we don't need the messages after it + break + } //TODO we should technically be normalizing the filecoin address of from when we compare here if tsMsg.VMMessage().From == msg.VMMessage().From { - filteredTsMsgs = append(filteredTsMsgs, tsMsg) + priorMsgs = append(priorMsgs, tsMsg) } } - priorMsgs = append(filteredTsMsgs, priorMsgs...) } for i, m := range priorMsgs { _, err = vmi.ApplyMessage(ctx, m) @@ -238,10 +242,19 @@ func (sm *StateManager) callInternal( msg.Nonce = fromActor.Nonce - // If the fee cap is set to zero, make gas free. - if msg.GasFeeCap.NilOrZero() { - // Now estimate with a new VM with no base fee. - vmopt.BaseFee = big.Zero() + if msg.GasFeeCap.IsZero() || flushAllBlocks { + // If the fee cap is set to zero, make gas free. + if msg.GasFeeCap.IsZero() { + vmopt.BaseFee = big.Zero() + } + + // If we're flushing all blocks, we only want to flush for the message we're + // applying, not the prior messages. + if flushAllBlocks { + vmopt.FlushAllBlocks = true + vmopt.Bstore = blockstore.NewAccumulator(vmopt.Bstore) + } + vmopt.StateBase = stateCid vmi, err = sm.newVM(ctx, vmopt) @@ -298,7 +311,7 @@ func (sm *StateManager) callInternal( errs = ret.ActorErr.Error() } - return &api.InvocResult{ + res := &api.InvocResult{ MsgCid: msg.Cid(), Msg: msg, MsgRct: &ret.MessageReceipt, @@ -306,17 +319,37 @@ func (sm *StateManager) callInternal( ExecutionTrace: ret.ExecutionTrace, Error: errs, Duration: ret.Duration, - }, err + } + + if flushAllBlocks { + acc, ok := vmopt.Bstore.(*blockstore.Accumulator) + if !ok { + return nil, xerrors.Errorf("expected accumulator blockstore, got %T", vmopt.Bstore) + } + blocks, err := acc.GetBlocks(ctx) + if err != nil { + return nil, xerrors.Errorf("getting blocks from accumulator: %w", err) + } + res.Blocks = make([]api.Block, len(blocks)) + for i, b := range blocks { + res.Blocks[i] = api.Block{ + Cid: b.Cid(), + Data: b.RawData(), + } + } + } + + return res, nil } var errHaltExecution = fmt.Errorf("halt") -func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid, cacheStore blockstore.Blockstore) (*types.Message, *vm.ApplyRet, error) { +func (sm *StateManager) Replay(ctx context.Context, ts *types.TipSet, mcid cid.Cid) (*types.Message, *vm.ApplyRet, error) { var finder messageFinder // message to find finder.mcid = mcid - _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true, cacheStore) + _, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, &finder, true) if err != nil && !errors.Is(err, errHaltExecution) { return nil, nil, xerrors.Errorf("unexpected error during execution: %w", err) } diff --git a/chain/stmgr/execute.go b/chain/stmgr/execute.go index ecf956b117a..985218ef467 100644 --- a/chain/stmgr/execute.go +++ b/chain/stmgr/execute.go @@ -82,7 +82,7 @@ func (sm *StateManager) tipSetState(ctx context.Context, ts *types.TipSet, recom } } - st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor, false, nil) + st, rec, err = sm.tsExec.ExecuteTipSet(ctx, sm, ts, sm.tsExecMonitor, false) if err != nil { return cid.Undef, cid.Undef, err } @@ -136,7 +136,7 @@ func tryLookupTipsetState(ctx context.Context, cs *store.ChainStore, ts *types.T } func (sm *StateManager) ExecutionTraceWithMonitor(ctx context.Context, ts *types.TipSet, em ExecMonitor) (cid.Cid, error) { - st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em, true, nil) + st, _, err := sm.tsExec.ExecuteTipSet(ctx, sm, ts, em, true) return st, err } diff --git a/chain/stmgr/forks_test.go b/chain/stmgr/forks_test.go index 4af90d40766..dcc6ac6e4d4 100644 --- a/chain/stmgr/forks_test.go +++ b/chain/stmgr/forks_test.go @@ -344,7 +344,7 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { } // Call always applies the message to the "next block" after the tipset's parent state. - ret, err = sm.Call(ctx, m, ts.TipSet.TipSet()) + ret, err = sm.Call(ctx, m, ts.TipSet.TipSet(), false) if parentHeight <= testForkHeight && currentHeight >= testForkHeight { require.Equal(t, ErrExpensiveFork, err) } else { @@ -359,7 +359,7 @@ func testForkRefuseCall(t *testing.T, nullsBefore, nullsAfter int) { require.NoError(t, err) require.True(t, ret.MsgRct.ExitCode.IsSuccess()) - ret, err = sm.Call(ctx, m, nil) + ret, err = sm.Call(ctx, m, nil, false) require.NoError(t, err) require.True(t, ret.MsgRct.ExitCode.IsSuccess()) } diff --git a/chain/stmgr/rpc/rpcstatemanager.go b/chain/stmgr/rpc/rpcstatemanager.go index fa311e1d4b9..06d888a41f0 100644 --- a/chain/stmgr/rpc/rpcstatemanager.go +++ b/chain/stmgr/rpc/rpcstatemanager.go @@ -52,7 +52,7 @@ func (s *RPCStateManager) ResolveToDeterministicAddress(ctx context.Context, add return s.gapi.StateAccountKey(ctx, addr, ts.Key()) } -func (s *RPCStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { +func (s *RPCStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) { return nil, xerrors.Errorf("RPCStateManager does not implement StateManager.Call") } diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 95b761f8f29..174ee8bf2d5 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -22,7 +22,6 @@ import ( "github.com/filecoin-project/specs-actors/v8/actors/migration/nv16" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/actors/adt" _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" @@ -47,7 +46,7 @@ var execTraceCacheSize = 16 var log = logging.Logger("statemgr") type StateManagerAPI interface { - Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) + Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) LoadActorTsk(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*types.Actor, error) LookupIDAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) @@ -120,7 +119,7 @@ func (m *migrationResultCache) Delete(ctx context.Context, root cid.Cid) { type Executor interface { NewActorRegistry() *vm.ActorRegistry - ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor, vmTracing bool, cacheStore blockstore.Blockstore) (stateroot cid.Cid, rectsroot cid.Cid, err error) + ExecuteTipSet(ctx context.Context, sm *StateManager, ts *types.TipSet, em ExecMonitor, vmTracing bool) (stateroot cid.Cid, rectsroot cid.Cid, err error) } type StateManager struct { diff --git a/cli/disputer.go b/cli/disputer.go index f6680daec66..b39b0d3cd7c 100644 --- a/cli/disputer.go +++ b/cli/disputer.go @@ -106,7 +106,7 @@ var disputerMsgCmd = &cli.Command{ Params: dpp, } - rslt, err := api.StateCall(ctx, dmsg, types.EmptyTSK) + rslt, err := api.StateCall(ctx, lapi.NewStateCallParams(dmsg, types.EmptyTSK).ToRaw()) if err != nil { return xerrors.Errorf("failed to simulate dispute: %w", err) } @@ -391,7 +391,7 @@ func makeDisputeWindowedPosts(ctx context.Context, api v0api.FullNode, dl minerD Params: dpp, } - rslt, err := api.StateCall(ctx, dispute, types.EmptyTSK) + rslt, err := api.StateCall(ctx, lapi.NewStateCallParams(dispute, types.EmptyTSK).ToRaw()) if err == nil && rslt.MsgRct.ExitCode == 0 { disputes = append(disputes, dispute) } diff --git a/cli/state.go b/cli/state.go index d55b21fe2dc..f3c28538478 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1365,13 +1365,13 @@ var StateCallCmd = &cli.Command{ } } - ret, err := api.StateCall(ctx, &types.Message{ + ret, err := api.StateCall(ctx, lapi.NewStateCallParams(&types.Message{ From: froma, To: toa, Value: types.BigInt(value), Method: abi.MethodNum(method), Params: params, - }, ts.Key()) + }, ts.Key()).ToRaw()) if err != nil { return fmt.Errorf("state call failed: %w", err) } diff --git a/cmd/lotus-shed/f3.go b/cmd/lotus-shed/f3.go index 3f4e340712c..98eaa9cfacf 100644 --- a/cmd/lotus-shed/f3.go +++ b/cmd/lotus-shed/f3.go @@ -25,6 +25,7 @@ import ( "github.com/filecoin-project/go-f3/manifest" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/lf3" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/chain/types/ethtypes" @@ -95,13 +96,13 @@ var f3CheckActivation = &cli.Command{ ContractAddress: cctx.String("contract"), ContractPollInterval: 15 * time.Second, } - api, closer, err := cliutil.GetFullNodeAPIV1(cctx) + lapi, closer, err := cliutil.GetFullNodeAPIV1(cctx) if err != nil { return fmt.Errorf("getting api: %w", err) } defer closer() ctx := cliutil.ReqContext(cctx) - prov, err := lf3.NewManifestProvider(ctx, &config, nil, nil, nil, api) + prov, err := lf3.NewManifestProvider(ctx, &config, nil, nil, nil, lapi) if err != nil { return fmt.Errorf("creating manifest proivder: %w", err) } @@ -150,13 +151,13 @@ var f3CheckActivationRaw = &cli.Command{ } ctx := cliutil.ReqContext(cctx) - api, closer, err := cliutil.GetFullNodeAPIV1(cctx) + lapi, closer, err := cliutil.GetFullNodeAPIV1(cctx) if err != nil { return fmt.Errorf("getting api: %w", err) } defer closer() - msgRes, err := api.StateCall(ctx, fMessage, types.EmptyTSK) + msgRes, err := lapi.StateCall(ctx, api.NewStateCallParams(fMessage, types.EmptyTSK).ToRaw()) if err != nil { return fmt.Errorf("state call error: %w", err) } @@ -265,13 +266,13 @@ var f3GenExplicitPower = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := cliutil.ReqContext(cctx) - api, closer, err := cliutil.GetFullNodeAPIV1(cctx) + lapi, closer, err := cliutil.GetFullNodeAPIV1(cctx) if err != nil { return fmt.Errorf("getting api: %w", err) } defer closer() - ts, err := lcli.LoadTipSet(ctx, cctx, api) + ts, err := lcli.LoadTipSet(ctx, cctx, lapi) if err != nil { return fmt.Errorf("getting chain head: %w", err) } @@ -279,7 +280,7 @@ var f3GenExplicitPower = &cli.Command{ return fmt.Errorf("n and ratio options are exclusive") } - allPowerEntries, err := api.F3GetECPowerTable(ctx, ts.Key()) + allPowerEntries, err := lapi.F3GetECPowerTable(ctx, ts.Key()) if err != nil { return fmt.Errorf("getting power entries: %w", err) } diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 1beebee723b..f8a78f8750d 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -93,6 +93,7 @@ func main() { blockCmd, adlCmd, f3Cmd, + findMsgCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index 2c1f2932ddd..c608890d4ed 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -25,6 +25,7 @@ import ( "github.com/filecoin-project/specs-actors/v2/actors/builtin/multisig" "github.com/filecoin-project/lotus/api" + lapi "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" @@ -50,6 +51,10 @@ var msgCmd = &cli.Command{ Name: "gas-stats", Usage: "Print a summary of gas charges", }, + &cli.BoolFlag{ + Name: "block-analysis", + Usage: "Perform and print an analysis of the blocks written during message execution", + }, }, Action: func(cctx *cli.Context) error { if cctx.NArg() != 1 { @@ -80,28 +85,19 @@ var msgCmd = &cli.Command{ if lookup == nil { fmt.Println("Message not found on-chain. Continuing...") } else { - // Replay the message to get the execution trace - res, err := api.StateReplay(ctx, types.EmptyTSK, mcid) - if err != nil { - return xerrors.Errorf("replay call failed: %w", err) - } + var res *lapi.InvocResult - /* - var fixSealPrice func(trace types.ExecutionTrace) - fixSealPrice = func(trace types.ExecutionTrace) { - for i := range trace.GasCharges { - if trace.GasCharges[i].Name == "OnVerifySeal" && trace.GasCharges[i].ComputeGas == 2000 { - // should be 42M - trace.GasCharges[i].ComputeGas = 42_000_000 - trace.GasCharges[i].TotalGas += 42_000_000 - 2000 - } - } - for i := range trace.Subcalls { - fixSealPrice(trace.Subcalls[i]) - } + if cctx.Bool("exec-trace") || cctx.Bool("gas-stats") || cctx.Bool("block-analysis") { + // Re-execute the message to get the trace + executionTs, err := api.ChainGetTipSet(ctx, lookup.TipSet) + if err != nil { + return xerrors.Errorf("getting tipset: %w", err) + } + res, err = api.StateCall(ctx, lapi.NewStateCallParams(msg.VMMessage(), executionTs.Parents()).WithIncludeBlocks().ToRaw()) + if err != nil { + return xerrors.Errorf("calling message: %w", err) } - fixSealPrice(res.ExecutionTrace) - */ + } if cctx.Bool("exec-trace") { // Print the execution trace @@ -113,18 +109,21 @@ var msgCmd = &cli.Command{ fmt.Println(string(trace)) fmt.Println() - if res.CachedBlocks != nil { - if err := saveAndInspectBlocks(ctx, res, cctx.App.Writer); err != nil { - return err - } - } - color.Green("Receipt:") fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode) fmt.Printf("Return: %x\n", res.MsgRct.Return) fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed) } + if cctx.Bool("block-analysis") { + if res.Blocks == nil { + return xerrors.New("no blocks found in execution trace") + } + if err := saveAndInspectBlocks(ctx, res, cctx.App.Writer); err != nil { + return err + } + } + if cctx.Bool("gas-stats") { var printTrace func(descPfx string, trace types.ExecutionTrace) error printTrace = func(descPfx string, trace types.ExecutionTrace) error { @@ -538,7 +537,7 @@ func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writ } }() - for _, b := range res.CachedBlocks { + for _, b := range res.Blocks { bc, err := blocks.NewBlockWithCid(b.Data, b.Cid) if err != nil { return xerrors.Errorf("creating cached block: %w", err) @@ -563,7 +562,7 @@ func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writ typ = "Subcall" } - blkStats := make([]blkStat, 0, len(res.CachedBlocks)) + blkStats := make([]blkStat, 0, len(res.Blocks)) var totalBytes, totalGas int for _, ll := range trace.Logs { diff --git a/cmd/lotus-shed/sectors.go b/cmd/lotus-shed/sectors.go index 892058c1096..8e068b66011 100644 --- a/cmd/lotus-shed/sectors.go +++ b/cmd/lotus-shed/sectors.go @@ -27,6 +27,7 @@ import ( "github.com/filecoin-project/go-state-types/builtin" miner2 "github.com/filecoin-project/specs-actors/v2/actors/builtin/miner" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/api/v0api" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" @@ -184,7 +185,7 @@ var terminateSectorPenaltyEstimationCmd = &cli.Command{ //TODO: 4667 add an option to give a more precise estimation with pending termination penalty excluded - invocResult, err := nodeApi.StateCall(ctx, msg, types.EmptyTSK) + invocResult, err := nodeApi.StateCall(ctx, api.NewStateCallParams(msg, types.EmptyTSK).ToRaw()) if err != nil { return xerrors.Errorf("fail to state call: %w", err) } diff --git a/cmd/tvx/state.go b/cmd/tvx/state.go index 9674bf17ed6..59b38bb3601 100644 --- a/cmd/tvx/state.go +++ b/cmd/tvx/state.go @@ -107,7 +107,7 @@ func (sg *StateSurgeon) GetAccessedActors(ctx context.Context, a v1api.FullNode, return nil, err } - trace, err := a.StateCall(ctx, msgObj, ts.Parents()) + trace, err := a.StateCall(ctx, api.NewStateCallParams(msgObj, ts.Parents()).ToRaw()) if err != nil { return nil, fmt.Errorf("could not replay msg: %w", err) } diff --git a/conformance/driver.go b/conformance/driver.go index 21f4628f984..15ae567063a 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -175,7 +175,6 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params params.Rand, recordOutputs, true, - nil, params.BaseFee, nil, ) diff --git a/gateway/node.go b/gateway/node.go index 349bfbfd3a1..b16c67d7a8f 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -83,7 +83,7 @@ type TargetAPI interface { MsigGetVestingSchedule(context.Context, address.Address, types.TipSetKey) (api.MsigVesting, error) MsigGetPending(ctx context.Context, addr address.Address, ts types.TipSetKey) ([]*api.MsigTransaction, error) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) - StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) + StateCall(ctx context.Context, p jsonrpc.RawParams) (*api.InvocResult, error) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) StateDecodeParams(ctx context.Context, toAddr address.Address, method abi.MethodNum, params []byte, tsk types.TipSetKey) (interface{}, error) StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) diff --git a/gateway/proxy_fil.go b/gateway/proxy_fil.go index a36c7fdc576..e1d3af97e24 100644 --- a/gateway/proxy_fil.go +++ b/gateway/proxy_fil.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" "github.com/filecoin-project/go-f3/certs" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" verifregtypes "github.com/filecoin-project/go-state-types/builtin/v9/verifreg" "github.com/filecoin-project/go-state-types/crypto" @@ -319,14 +320,18 @@ func (gw *Node) StateAccountKey(ctx context.Context, addr address.Address, tsk t return gw.target.StateAccountKey(ctx, addr, tsk) } -func (gw *Node) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (*api.InvocResult, error) { +func (gw *Node) StateCall(ctx context.Context, p jsonrpc.RawParams) (*api.InvocResult, error) { if err := gw.limit(ctx, stateRateLimitTokens); err != nil { return nil, err } - if err := gw.checkTipsetKey(ctx, tsk); err != nil { + params, err := api.StateCallParamsFromRaw(p) + if err != nil { + return nil, err + } + if err := gw.checkTipsetKey(ctx, params.TipSetKey); err != nil { return nil, err } - return gw.target.StateCall(ctx, msg, tsk) + return gw.target.StateCall(ctx, p) } func (gw *Node) StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) { diff --git a/itests/remove_verifreg_datacap_test.go b/itests/remove_verifreg_datacap_test.go index bb531733e0a..d0da4d9a86d 100644 --- a/itests/remove_verifreg_datacap_test.go +++ b/itests/remove_verifreg_datacap_test.go @@ -263,13 +263,13 @@ func TestNoRemoveDatacapFromVerifreg(t *testing.T) { params, aerr := actors.SerializeParams(&removeDataCapParams) require.NoError(t, aerr) - callResult, err := clientApi.StateCall(ctx, &types.Message{ + callResult, err := clientApi.StateCall(ctx, api.NewStateCallParams(&types.Message{ From: rootAddr, To: verifreg.Address, Method: verifreg.Methods.RemoveVerifiedClientDataCap, Params: params, Value: big.Zero(), - }, types.EmptyTSK) + }, types.EmptyTSK).ToRaw()) require.NoError(t, err) require.False(t, callResult.MsgRct.ExitCode.IsSuccess()) diff --git a/itests/wdpost_test.go b/itests/wdpost_test.go index 3f130cc1948..71d3a2bda2c 100644 --- a/itests/wdpost_test.go +++ b/itests/wdpost_test.go @@ -320,7 +320,7 @@ waitForProof: slmsg.Params = v1PostParams.Bytes() // Simulate call on inclTs's parents, so that the partition isn't already proven - ret, err := client.StateCall(ctx, slmsg, inclTs.Parents()) + ret, err := client.StateCall(ctx, api.NewStateCallParams(slmsg, inclTs.Parents()).ToRaw()) require.NoError(t, err) require.Contains(t, ret.Error, "expected proof of type StackedDRGWindow2KiBV1P1, got StackedDRGWindow2KiBV1") diff --git a/node/impl/eth/api.go b/node/impl/eth/api.go index b9a5159a3e9..99e29b2a0c8 100644 --- a/node/impl/eth/api.go +++ b/node/impl/eth/api.go @@ -69,7 +69,7 @@ type StateManager interface { ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) ExecutionTrace(ctx context.Context, ts *types.TipSet) (cid.Cid, []*api.InvocResult, error) - Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) + Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) CallWithGas(ctx context.Context, msg *types.Message, priorMsgs []types.ChainMsg, ts *types.TipSet, applyTsMessages bool) (*api.InvocResult, error) ApplyOnStateWithGas(ctx context.Context, stateCid cid.Cid, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) diff --git a/node/impl/eth/lookup.go b/node/impl/eth/lookup.go index 94f2775bf0f..02195efa793 100644 --- a/node/impl/eth/lookup.go +++ b/node/impl/eth/lookup.go @@ -100,7 +100,7 @@ func (e *ethLookup) EthGetCode(ctx context.Context, ethAddr ethtypes.EthAddress, // Try calling until we find a height with no migration. var res *api.InvocResult for { - res, err = e.stateManager.Call(ctx, msg, ts) + res, err = e.stateManager.Call(ctx, msg, ts, false) if err != stmgr.ErrExpensiveFork { break } @@ -198,7 +198,7 @@ func (e *ethLookup) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAdd // Try calling until we find a height with no migration. var res *api.InvocResult for { - res, err = e.stateManager.Call(ctx, msg, ts) + res, err = e.stateManager.Call(ctx, msg, ts, false) if err != stmgr.ErrExpensiveFork { break } diff --git a/node/impl/full/state.go b/node/impl/full/state.go index 6f4a9c09fc4..e079d3e99cf 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -7,9 +7,7 @@ import ( "errors" "fmt" "math" - "os" "strconv" - "strings" "github.com/ipfs/go-cid" "github.com/libp2p/go-libp2p/core/peer" @@ -19,6 +17,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-bitfield" + "github.com/filecoin-project/go-jsonrpc" "github.com/filecoin-project/go-state-types/abi" actorstypes "github.com/filecoin-project/go-state-types/actors" "github.com/filecoin-project/go-state-types/big" @@ -31,7 +30,6 @@ import ( market5 "github.com/filecoin-project/specs-actors/v5/actors/builtin/market" "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/blockstore" "github.com/filecoin-project/lotus/build/buildconstants" "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/actors/builtin" @@ -74,7 +72,7 @@ type StateModuleAPI interface { StateVerifiedClientStatus(ctx context.Context, addr address.Address, tsk types.TipSetKey) (*abi.StoragePower, error) StateSearchMsg(ctx context.Context, from types.TipSetKey, msg cid.Cid, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) StateWaitMsg(ctx context.Context, cid cid.Cid, confidence uint64, limit abi.ChainEpoch, allowReplaced bool) (*api.MsgLookup, error) - StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) + StateCall(ctx context.Context, p jsonrpc.RawParams) (res *api.InvocResult, err error) } var _ StateModuleAPI = *new(api.FullNode) @@ -109,10 +107,6 @@ type StateAPI struct { TsExec stmgr.Executor } -const ReplayDumpCachedBlocksKey = "LOTUS_REPLAY_DUMP_CACHED_BLOCKS" - -var replayDumpCachedBlocks bool - func (a *StateAPI) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) { return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState()) } @@ -150,13 +144,17 @@ func (a *StateAPI) StateMinerActiveSectors(ctx context.Context, maddr address.Ad return mas.LoadSectors(&activeSectors) } -func (m *StateModule) StateCall(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) { - ts, err := m.Chain.GetTipSetFromKey(ctx, tsk) +func (m *StateModule) StateCall(ctx context.Context, p jsonrpc.RawParams) (res *api.InvocResult, err error) { + params, err := api.StateCallParamsFromRaw(p) if err != nil { - return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) + return nil, xerrors.Errorf("decoding params: %w", err) + } + ts, err := m.Chain.GetTipSetFromKey(ctx, params.TipSetKey) + if err != nil { + return nil, xerrors.Errorf("loading tipset %s: %w", params.TipSetKey, err) } for { - res, err = m.StateManager.Call(ctx, msg, ts) + res, err = m.StateManager.Call(ctx, params.Message, ts, params.IncludeBlocks) if err != stmgr.ErrExpensiveFork { break } @@ -464,11 +462,7 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. } } - var cacheStore blockstore.Blockstore - if replayDumpCachedBlocks { - cacheStore = blockstore.NewMemory() - } - m, r, err := a.StateManager.Replay(ctx, ts, msgToReplay, cacheStore) + m, r, err := a.StateManager.Replay(ctx, ts, msgToReplay) if err != nil { return nil, err } @@ -487,16 +481,7 @@ func (a *StateAPI) StateReplay(ctx context.Context, tsk types.TipSetKey, mc cid. Error: errstr, Duration: r.Duration, } - if replayDumpCachedBlocks { - bs := cacheStore.(blockstore.MemBlockstore) - result.CachedBlocks = make([]api.Block, 0, len(bs)) - for _, blk := range bs { - result.CachedBlocks = append(result.CachedBlocks, api.Block{ - Cid: blk.Cid(), - Data: blk.RawData(), - }) - } - } + return result, nil } @@ -1035,7 +1020,7 @@ func (a *StateAPI) stateComputeDataCIDv1(ctx context.Context, maddr address.Addr Method: 8, Params: ccparams, } - r, err := a.StateCall(ctx, ccmt, tsk) + r, err := a.StateCall(ctx, api.NewStateCallParams(ccmt, tsk).ToRaw()) if err != nil { return cid.Undef, xerrors.Errorf("calling ComputeDataCommitment: %w", err) } @@ -1073,7 +1058,7 @@ func (a *StateAPI) stateComputeDataCIDv2(ctx context.Context, maddr address.Addr Method: 8, Params: ccparams, } - r, err := a.StateCall(ctx, ccmt, tsk) + r, err := a.StateCall(ctx, api.NewStateCallParams(ccmt, tsk).ToRaw()) if err != nil { return cid.Undef, xerrors.Errorf("calling ComputeDataCommitment: %w", err) } @@ -1117,7 +1102,7 @@ func (a *StateAPI) stateComputeDataCIDv3(ctx context.Context, maddr address.Addr Method: market.Methods.VerifyDealsForActivation, Params: ccparams, } - r, err := a.StateCall(ctx, ccmt, tsk) + r, err := a.StateCall(ctx, api.NewStateCallParams(ccmt, tsk).ToRaw()) if err != nil { return cid.Undef, xerrors.Errorf("calling VerifyDealsForActivation: %w", err) } @@ -2119,15 +2104,3 @@ func (a *StateAPI) StateGetNetworkParams(ctx context.Context) (*api.NetworkParam }, }, nil } - -func init() { - replayDumpCachedBlocks = (func() bool { - v, _ := os.LookupEnv(ReplayDumpCachedBlocksKey) - switch strings.TrimSpace(strings.ToLower(v)) { - case "", "0", "false", "no": // Consider these values as "do not enable". - return false - default: - return true - } - })() -} diff --git a/paychmgr/manager.go b/paychmgr/manager.go index 13876e1d490..4271737aade 100644 --- a/paychmgr/manager.go +++ b/paychmgr/manager.go @@ -31,7 +31,7 @@ var errProofNotSupported = errors.New("payment channel proof parameter is not su type stateManagerAPI interface { ResolveToDeterministicAddress(ctx context.Context, addr address.Address, ts *types.TipSet) (address.Address, error) GetPaychState(ctx context.Context, addr address.Address, ts *types.TipSet) (*types.Actor, paych.State, error) - Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) + Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) } // PaychAPI defines the API methods needed by the payment channel manager diff --git a/paychmgr/mock_test.go b/paychmgr/mock_test.go index 265f05dc010..5a69b9fed80 100644 --- a/paychmgr/mock_test.go +++ b/paychmgr/mock_test.go @@ -97,7 +97,7 @@ func (sm *mockStateManager) getLastCall() *types.Message { return sm.lastCall } -func (sm *mockStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet) (*api.InvocResult, error) { +func (sm *mockStateManager) Call(ctx context.Context, msg *types.Message, ts *types.TipSet, flushAllBlocks bool) (*api.InvocResult, error) { sm.lk.Lock() defer sm.lk.Unlock() diff --git a/paychmgr/paych.go b/paychmgr/paych.go index 9bbd3b39976..d7feb5579fe 100644 --- a/paychmgr/paych.go +++ b/paychmgr/paych.go @@ -324,7 +324,7 @@ func (ca *channelAccessor) checkVoucherSpendable(ctx context.Context, ch address return false, err } - ret, err := ca.api.Call(ctx, mes, nil) + ret, err := ca.api.Call(ctx, mes, nil, false) if err != nil { return false, err } From 84eb77723c4a16fd3d06a672269902ff7b469eb9 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 28 Mar 2025 19:31:11 +1100 Subject: [PATCH 7/8] chore: use filecoin-ffi branch --- extern/filecoin-ffi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 037aa5ce119..285faf5ab2b 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 037aa5ce11958eb2b47799b4cf16922874f6e2e1 +Subproject commit 285faf5ab2b6e6a219b7fe2114e54c5a45fce3cf From fb473a5833fe0ad1808291ae6092e5f68d239174 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Thu, 3 Apr 2025 18:10:07 +1100 Subject: [PATCH 8/8] feat: big 'ol refactor & improvement of lotus-shed msg --- api/docgen/docgen.go | 6 + api/mocks/mock_full.go | 4 +- api/v0api/proxy_gen.go | 2 +- api/v0api/v0mocks/mock_full.go | 10 +- build/openrpc/full.json | 222 ++++++---- build/openrpc/gateway.json | 179 ++++---- chain/stmgr/call.go | 3 + chain/types/cbor_gen.go | 158 ++++++- chain/types/execresult.go | 68 +++ chain/vm/fvm.go | 1 + cmd/lotus-shed/block-matcher.go | 22 +- cmd/lotus-shed/eth.go | 10 +- cmd/lotus-shed/main.go | 1 - cmd/lotus-shed/msg.go | 458 +++++++++++--------- documentation/en/api-v0-methods.md | 84 ++-- documentation/en/api-v1-unstable-methods.md | 84 ++-- extern/filecoin-ffi | 2 +- gen/main.go | 1 + 18 files changed, 871 insertions(+), 444 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 035bc6559a8..30189b1b94f 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -181,6 +181,12 @@ func init() { addExample(map[string]types.Actor{ "t01236": ExampleValue("init", reflect.TypeOf(types.Actor{}), nil).(types.Actor), }) + addExample(types.IpldOpGet) + addExample(&types.TraceIpld{ + Op: types.IpldOpGet, + Cid: c, + Size: 123, + }) addExample(&types.ExecutionTrace{ Msg: ExampleValue("init", reflect.TypeOf(types.MessageTrace{}), nil).(types.MessageTrace), MsgRct: ExampleValue("init", reflect.TypeOf(types.ReturnTrace{}), nil).(types.ReturnTrace), diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 3d5d52a1871..20f426d7f7b 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -2877,9 +2877,9 @@ func (m *MockFullNode) StateCall(arg0 context.Context, arg1 jsonrpc.RawParams) ( } // StateCall indicates an expected call of StateCall. -func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1) } // StateChangedActors mocks base method. diff --git a/api/v0api/proxy_gen.go b/api/v0api/proxy_gen.go index 9bcbf93e4ea..130e78365c9 100644 --- a/api/v0api/proxy_gen.go +++ b/api/v0api/proxy_gen.go @@ -415,7 +415,7 @@ type GatewayMethods struct { StateAccountKey func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `` - StateCall func(p0 context.Context, p jsonrpc.RawParams) (*api.InvocResult, error) `` + StateCall func(p0 context.Context, p1 jsonrpc.RawParams) (*api.InvocResult, error) `` StateDealProviderCollateralBounds func(p0 context.Context, p1 abi.PaddedPieceSize, p2 bool, p3 types.TipSetKey) (api.DealCollateralBounds, error) `` diff --git a/api/v0api/v0mocks/mock_full.go b/api/v0api/v0mocks/mock_full.go index 34a761dca1f..8311d6474fe 100644 --- a/api/v0api/v0mocks/mock_full.go +++ b/api/v0api/v0mocks/mock_full.go @@ -1,3 +1,7 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/filecoin-project/lotus/api/v0api (interfaces: FullNode) + +// Package v0mocks is a generated GoMock package. package v0mocks import ( @@ -16,7 +20,7 @@ import ( address "github.com/filecoin-project/go-address" bitfield "github.com/filecoin-project/go-bitfield" - "github.com/filecoin-project/go-jsonrpc" + jsonrpc "github.com/filecoin-project/go-jsonrpc" auth "github.com/filecoin-project/go-jsonrpc/auth" abi "github.com/filecoin-project/go-state-types/abi" big "github.com/filecoin-project/go-state-types/big" @@ -1837,9 +1841,9 @@ func (m *MockFullNode) StateCall(arg0 context.Context, arg1 jsonrpc.RawParams) ( } // StateCall indicates an expected call of StateCall. -func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1, arg2 interface{}) *gomock.Call { +func (mr *MockFullNodeMockRecorder) StateCall(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1, arg2) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateCall", reflect.TypeOf((*MockFullNode)(nil).StateCall), arg0, arg1) } // StateChangedActors mocks base method. diff --git a/build/openrpc/full.json b/build/openrpc/full.json index e5cb2590759..26488a1aca6 100644 --- a/build/openrpc/full.json +++ b/build/openrpc/full.json @@ -17432,102 +17432,29 @@ }, { "name": "Filecoin.StateCall", - "description": "```go\nfunc (s *FullNodeStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) {\n\tif s.Internal.StateCall == nil {\n\t\treturn nil, ErrNotSupported\n\t}\n\treturn s.Internal.StateCall(p0, p1, p2)\n}\n```", + "description": "```go\nfunc (s *FullNodeStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) {\n\tif s.Internal.StateCall == nil {\n\t\treturn nil, ErrNotSupported\n\t}\n\treturn s.Internal.StateCall(p0, p1)\n}\n```", "summary": "StateCall runs the given message and returns its result without any persisted changes.\n\nStateCall applies the message to the tipset's parent state. The\nmessage is not applied on-top-of the messages in the passed-in\ntipset.\n", "paramStructure": "by-position", "params": [ { "name": "p1", - "description": "*types.Message", + "description": "jsonrpc.RawParams", "summary": "", "schema": { "examples": [ - { - "Version": 42, - "To": "f01234", - "From": "f01234", - "Nonce": 42, - "Value": "0", - "GasLimit": 9, - "GasFeeCap": "0", - "GasPremium": "0", - "Method": 1, - "Params": "Ynl0ZSBhcnJheQ==", - "CID": { - "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" - } - } + "Bw==" ], - "additionalProperties": false, - "properties": { - "From": { - "additionalProperties": false, - "type": "object" - }, - "GasFeeCap": { - "additionalProperties": false, - "type": "object" - }, - "GasLimit": { - "title": "number", - "type": "number" - }, - "GasPremium": { - "additionalProperties": false, - "type": "object" - }, - "Method": { - "title": "number", - "type": "number" - }, - "Nonce": { - "title": "number", - "type": "number" - }, - "Params": { - "media": { - "binaryEncoding": "base64" - }, - "type": "string" - }, - "To": { - "additionalProperties": false, - "type": "object" - }, - "Value": { - "additionalProperties": false, - "type": "object" - }, - "Version": { + "items": [ + { "title": "number", - "type": "number" + "description": "Number is a number", + "type": [ + "number" + ] } - }, - "type": [ - "object" - ] - }, - "required": true, - "deprecated": false - }, - { - "name": "p2", - "description": "types.TipSetKey", - "summary": "", - "schema": { - "examples": [ - [ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" - } - ] ], - "additionalProperties": false, "type": [ - "object" + "array" ] }, "required": true, @@ -17661,16 +17588,34 @@ "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -17682,7 +17627,7 @@ ], "additionalProperties": false, "properties": { - "CachedBlocks": { + "Blocks": { "items": { "additionalProperties": false, "properties": { @@ -17775,6 +17720,27 @@ }, "type": "object" }, + "IpldOps": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Op": { + "title": "number", + "type": "number" + }, + "Size": { + "title": "number", + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, "Logs": { "items": { "type": "string" @@ -18395,16 +18361,34 @@ "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -18426,7 +18410,7 @@ "items": { "additionalProperties": false, "properties": { - "CachedBlocks": { + "Blocks": { "items": { "additionalProperties": false, "properties": { @@ -18519,6 +18503,27 @@ }, "type": "object" }, + "IpldOps": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Op": { + "title": "number", + "type": "number" + }, + "Size": { + "title": "number", + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, "Logs": { "items": { "type": "string" @@ -23785,16 +23790,34 @@ "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -23806,7 +23829,7 @@ ], "additionalProperties": false, "properties": { - "CachedBlocks": { + "Blocks": { "items": { "additionalProperties": false, "properties": { @@ -23899,6 +23922,27 @@ }, "type": "object" }, + "IpldOps": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Op": { + "title": "number", + "type": "number" + }, + "Size": { + "title": "number", + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, "Logs": { "items": { "type": "string" diff --git a/build/openrpc/gateway.json b/build/openrpc/gateway.json index c1d89585f4b..591b6313064 100644 --- a/build/openrpc/gateway.json +++ b/build/openrpc/gateway.json @@ -8257,102 +8257,29 @@ }, { "name": "Filecoin.StateCall", - "description": "```go\nfunc (s *GatewayStruct) StateCall(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (*InvocResult, error) {\n\tif s.Internal.StateCall == nil {\n\t\treturn nil, ErrNotSupported\n\t}\n\treturn s.Internal.StateCall(p0, p1, p2)\n}\n```", + "description": "```go\nfunc (s *GatewayStruct) StateCall(p0 context.Context, p1 jsonrpc.RawParams) (*InvocResult, error) {\n\tif s.Internal.StateCall == nil {\n\t\treturn nil, ErrNotSupported\n\t}\n\treturn s.Internal.StateCall(p0, p1)\n}\n```", "summary": "There are not yet any comments for this method.", "paramStructure": "by-position", "params": [ { "name": "p1", - "description": "*types.Message", + "description": "jsonrpc.RawParams", "summary": "", "schema": { "examples": [ - { - "Version": 42, - "To": "f01234", - "From": "f01234", - "Nonce": 42, - "Value": "0", - "GasLimit": 9, - "GasFeeCap": "0", - "GasPremium": "0", - "Method": 1, - "Params": "Ynl0ZSBhcnJheQ==", - "CID": { - "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" - } - } + "Bw==" ], - "additionalProperties": false, - "properties": { - "From": { - "additionalProperties": false, - "type": "object" - }, - "GasFeeCap": { - "additionalProperties": false, - "type": "object" - }, - "GasLimit": { - "title": "number", - "type": "number" - }, - "GasPremium": { - "additionalProperties": false, - "type": "object" - }, - "Method": { - "title": "number", - "type": "number" - }, - "Nonce": { - "title": "number", - "type": "number" - }, - "Params": { - "media": { - "binaryEncoding": "base64" - }, - "type": "string" - }, - "To": { - "additionalProperties": false, - "type": "object" - }, - "Value": { - "additionalProperties": false, - "type": "object" - }, - "Version": { + "items": [ + { "title": "number", - "type": "number" + "description": "Number is a number", + "type": [ + "number" + ] } - }, - "type": [ - "object" - ] - }, - "required": true, - "deprecated": false - }, - { - "name": "p2", - "description": "types.TipSetKey", - "summary": "", - "schema": { - "examples": [ - [ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" - } - ] ], - "additionalProperties": false, "type": [ - "object" + "array" ] }, "required": true, @@ -8486,16 +8413,34 @@ "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -8507,7 +8452,7 @@ ], "additionalProperties": false, "properties": { - "CachedBlocks": { + "Blocks": { "items": { "additionalProperties": false, "properties": { @@ -8600,6 +8545,27 @@ }, "type": "object" }, + "IpldOps": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Op": { + "title": "number", + "type": "number" + }, + "Size": { + "title": "number", + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, "Logs": { "items": { "type": "string" @@ -11239,16 +11205,34 @@ "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -11260,7 +11244,7 @@ ], "additionalProperties": false, "properties": { - "CachedBlocks": { + "Blocks": { "items": { "additionalProperties": false, "properties": { @@ -11353,6 +11337,27 @@ }, "type": "object" }, + "IpldOps": { + "items": { + "additionalProperties": false, + "properties": { + "Cid": { + "title": "Content Identifier", + "type": "string" + }, + "Op": { + "title": "number", + "type": "number" + }, + "Size": { + "title": "number", + "type": "number" + } + }, + "type": "object" + }, + "type": "array" + }, "Logs": { "items": { "type": "string" diff --git a/chain/stmgr/call.go b/chain/stmgr/call.go index 40939752001..0cf291337a2 100644 --- a/chain/stmgr/call.go +++ b/chain/stmgr/call.go @@ -322,6 +322,9 @@ func (sm *StateManager) callInternal( } if flushAllBlocks { + if _, err := vmi.Flush(ctx); err != nil { + return nil, xerrors.Errorf("flushing vm: %w", err) + } acc, ok := vmopt.Bstore.(*blockstore.Accumulator) if !ok { return nil, xerrors.Errorf("expected accumulator blockstore, got %T", vmopt.Bstore) diff --git a/chain/types/cbor_gen.go b/chain/types/cbor_gen.go index 632f4309a26..358b2f2a55d 100644 --- a/chain/types/cbor_gen.go +++ b/chain/types/cbor_gen.go @@ -2823,7 +2823,7 @@ func (t *ReturnTrace) UnmarshalCBOR(r io.Reader) (err error) { return nil } -var lengthBufExecutionTrace = []byte{134} +var lengthBufExecutionTrace = []byte{135} func (t *ExecutionTrace) MarshalCBOR(w io.Writer) error { if t == nil { @@ -2902,6 +2902,21 @@ func (t *ExecutionTrace) MarshalCBOR(w io.Writer) error { return err } + } + + // t.IpldOps ([]types.TraceIpld) (slice) + if len(t.IpldOps) > 1000000000 { + return xerrors.Errorf("Slice value in field t.IpldOps was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.IpldOps))); err != nil { + return err + } + for _, v := range t.IpldOps { + if err := v.MarshalCBOR(cw); err != nil { + return err + } + } return nil } @@ -2925,7 +2940,7 @@ func (t *ExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { return fmt.Errorf("cbor input should be of type array") } - if extra != 6 { + if extra != 7 { return fmt.Errorf("cbor input had wrong number of fields") } @@ -3091,5 +3106,144 @@ func (t *ExecutionTrace) UnmarshalCBOR(r io.Reader) (err error) { } } + // t.IpldOps ([]types.TraceIpld) (slice) + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 1000000000 { + return fmt.Errorf("t.IpldOps: array too large (%d)", extra) + } + + if maj != cbg.MajArray { + return fmt.Errorf("expected cbor array") + } + + if extra > 0 { + t.IpldOps = make([]TraceIpld, extra) + } + + for i := 0; i < int(extra); i++ { + { + var maj byte + var extra uint64 + var err error + _ = maj + _ = extra + _ = err + + { + + if err := t.IpldOps[i].UnmarshalCBOR(cr); err != nil { + return xerrors.Errorf("unmarshaling t.IpldOps[i]: %w", err) + } + + } + + } + } + return nil +} + +var lengthBufTraceIpld = []byte{131} + +func (t *TraceIpld) MarshalCBOR(w io.Writer) error { + if t == nil { + _, err := w.Write(cbg.CborNull) + return err + } + + cw := cbg.NewCborWriter(w) + + if _, err := cw.Write(lengthBufTraceIpld); err != nil { + return err + } + + // t.Op (types.Op) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Op)); err != nil { + return err + } + + // t.Cid (cid.Cid) (struct) + + if err := cbg.WriteCid(cw, t.Cid); err != nil { + return xerrors.Errorf("failed to write cid field t.Cid: %w", err) + } + + // t.Size (uint64) (uint64) + + if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Size)); err != nil { + return err + } + + return nil +} + +func (t *TraceIpld) UnmarshalCBOR(r io.Reader) (err error) { + *t = TraceIpld{} + + cr := cbg.NewCborReader(r) + + maj, extra, err := cr.ReadHeader() + if err != nil { + return err + } + defer func() { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + }() + + if maj != cbg.MajArray { + return fmt.Errorf("cbor input should be of type array") + } + + if extra != 3 { + return fmt.Errorf("cbor input had wrong number of fields") + } + + // t.Op (types.Op) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Op = Op(extra) + + } + // t.Cid (cid.Cid) (struct) + + { + + c, err := cbg.ReadCid(cr) + if err != nil { + return xerrors.Errorf("failed to read cid field t.Cid: %w", err) + } + + t.Cid = c + + } + // t.Size (uint64) (uint64) + + { + + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + if maj != cbg.MajUnsignedInt { + return fmt.Errorf("wrong type for uint64 field") + } + t.Size = uint64(extra) + + } return nil } diff --git a/chain/types/execresult.go b/chain/types/execresult.go index c32a4db6a96..ab5a0dfee10 100644 --- a/chain/types/execresult.go +++ b/chain/types/execresult.go @@ -2,8 +2,11 @@ package types import ( "encoding/json" + "fmt" "time" + "github.com/ipfs/go-cid" + "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" @@ -39,6 +42,70 @@ type ReturnTrace struct { ReturnCodec uint64 } +type Op uint64 + +const ( + IpldOpGet Op = iota + IpldOpPut +) + +type TraceIpld struct { + Op Op + Cid cid.Cid + Size uint64 +} + +func (t TraceIpld) MarshalJSON() ([]byte, error) { + type TraceIpldJSON struct { + Op string + Cid cid.Cid + Size uint64 + } + + var opStr string + switch t.Op { + case IpldOpGet: + opStr = "Get" + case IpldOpPut: + opStr = "Put" + default: + opStr = "Unknown" + } + + return json.Marshal(TraceIpldJSON{ + Op: opStr, + Cid: t.Cid, + Size: t.Size, + }) +} + +func (t *TraceIpld) UnmarshalJSON(data []byte) error { + type TraceIpldJSON struct { + Op string + Cid cid.Cid + Size uint64 + } + + var tj TraceIpldJSON + if err := json.Unmarshal(data, &tj); err != nil { + return err + } + + t.Cid = tj.Cid + t.Size = tj.Size + + switch tj.Op { + case "Get": + t.Op = IpldOpGet + case "Put": + t.Op = IpldOpPut + default: + return fmt.Errorf("unknown operation: %s", tj.Op) + } + + return nil +} + type ExecutionTrace struct { Msg MessageTrace MsgRct ReturnTrace @@ -46,6 +113,7 @@ type ExecutionTrace struct { GasCharges []*GasTrace `cborgen:"maxlen=1000000000"` Subcalls []ExecutionTrace `cborgen:"maxlen=1000000000"` Logs []string `cborgen:"maxlen=1000000000" json:",omitempty"` + IpldOps []TraceIpld `cborgen:"maxlen=1000000000" json:",omitempty"` } func (et ExecutionTrace) SumGas() GasTrace { diff --git a/chain/vm/fvm.go b/chain/vm/fvm.go index 77103d31d8d..091e4b6c494 100644 --- a/chain/vm/fvm.go +++ b/chain/vm/fvm.go @@ -282,6 +282,7 @@ func defaultFVMOpts(ctx context.Context, opts *VMOpts) (*ffi.FVMOpts, error) { StateBase: opts.StateBase, Tracing: opts.Tracing || EnableDetailedTracing, Debug: buildconstants.ActorDebugging, + FlushAllBlocks: opts.FlushAllBlocks, }, nil } diff --git a/cmd/lotus-shed/block-matcher.go b/cmd/lotus-shed/block-matcher.go index a7c2c54aab4..7cca0a63390 100644 --- a/cmd/lotus-shed/block-matcher.go +++ b/cmd/lotus-shed/block-matcher.go @@ -4,6 +4,8 @@ import ( "bytes" "context" "fmt" + "slices" + "strings" blocks "github.com/ipfs/go-block-format" cbor "github.com/ipfs/go-ipld-cbor" @@ -22,6 +24,7 @@ import ( "github.com/filecoin-project/go-hamt-ipld/v3" "github.com/filecoin-project/go-state-types/abi" gstbuiltin "github.com/filecoin-project/go-state-types/builtin" + miner15 "github.com/filecoin-project/go-state-types/builtin/v15/miner" datacap16 "github.com/filecoin-project/go-state-types/builtin/v16/datacap" market16 "github.com/filecoin-project/go-state-types/builtin/v16/market" miner16 "github.com/filecoin-project/go-state-types/builtin/v16/miner" @@ -103,7 +106,7 @@ func matchKnownBlockType(ctx context.Context, nd blocks.Block) (string, error) { // given a datamodel.Node form of the Values array within an AMT node, attempt to determine the // type of the values by iterating through them all and checking from their bytes. func matchAmtValues(values datamodel.Node) (string, error) { - var match string + var match []string itr := values.ListIterator() for !itr.Done() { _, v, err := itr.Next() @@ -115,10 +118,10 @@ func matchAmtValues(values datamodel.Node) (string, error) { return "", err } if m, _ := matchKnownBlockTypeFromBytes(enc); m != "" { - if match == "" { - match = m - } else if match != m { - return "", xerrors.Errorf("inconsistent types in AMT values") + if len(match) == 0 { + match = append(match, m) + } else if !slices.Contains(match, m) { + match = append(match, m) } // To debug unknown AMT types, uncomment this block: // } else { @@ -126,8 +129,8 @@ func matchAmtValues(values datamodel.Node) (string, error) { // return "", xerrors.Errorf("unknown type in AMT values: %s", enc) } } - if match != "" { - return "[" + match + "]", nil + if len(match) > 0 { + return "[" + strings.Join(match, " | ") + "]", nil } return "", nil } @@ -218,14 +221,17 @@ func matchKnownBlockTypeFromBytes(b []byte) (string, error) { known := map[string]cbg.CBORUnmarshaler{ // Fill this out with known types when you see them missing and can identify them "BlockHeader": &types.BlockHeader{}, + "miner15.State": &miner15.State{}, "miner16.State": &miner16.State{}, "miner16.MinerInfo": &miner16.MinerInfo{}, "miner16.Deadlines": &miner16.Deadlines{}, + "miner15.Deadline": &miner15.Deadline{}, "miner16.Deadline": &miner16.Deadline{}, "miner16.Partition": &miner16.Partition{}, + "miner15.ExpirationSet": &miner15.ExpirationSet{}, "miner16.ExpirationSet": &miner16.ExpirationSet{}, "miner16.WindowedPoSt": &miner16.WindowedPoSt{}, - "miner16.SectorOnChainInfo": &miner16.SectorOnChainInfo{}, + "miner16.SectorOnChainInfo": &miner16.SectorOnChainInfo{}, // 16 will also match 15 "miner16.SectorPreCommitOnChainInfo": &miner16.SectorPreCommitOnChainInfo{}, "power16.State": &power16.State{}, "market16.State": &market16.State{}, diff --git a/cmd/lotus-shed/eth.go b/cmd/lotus-shed/eth.go index 46bd1b43c29..ddd74171824 100644 --- a/cmd/lotus-shed/eth.go +++ b/cmd/lotus-shed/eth.go @@ -81,7 +81,15 @@ var computeEthHashCmd = &cli.Command{ return lcli.IncorrectNumArgs(cctx) } - msg, err := messageFromString(cctx, cctx.Args().First()) + api, closer, err := lcli.GetFullNodeAPIV1(cctx) + if err != nil { + return err + } + defer closer() + + ctx := lcli.ReqContext(cctx) + + msg, err := messageFromString(ctx, api, cctx.Args().First()) if err != nil { return err } diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index f8a78f8750d..1beebee723b 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -93,7 +93,6 @@ func main() { blockCmd, adlCmd, f3Cmd, - findMsgCmd, } app := &cli.App{ diff --git a/cmd/lotus-shed/msg.go b/cmd/lotus-shed/msg.go index c608890d4ed..bb0d7a92582 100644 --- a/cmd/lotus-shed/msg.go +++ b/cmd/lotus-shed/msg.go @@ -11,7 +11,6 @@ import ( "os" "path" "sort" - "strings" "github.com/fatih/color" blocks "github.com/ipfs/go-block-format" @@ -26,16 +25,19 @@ import ( "github.com/filecoin-project/lotus/api" lapi "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/api/v1api" "github.com/filecoin-project/lotus/chain/consensus" "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/lib/tablewriter" ) +const MultisigMagicNumber = 0x6d736967 + var msgCmd = &cli.Command{ Name: "message", Aliases: []string{"msg"}, - Usage: "Translate message between various formats", + Usage: "Inspect a message, either on chain (by CID) or attempt to decode raw bytes (hex, base64, json)", ArgsUsage: "Message in any form", Flags: []cli.Flag{ &cli.BoolFlag{ @@ -43,10 +45,20 @@ var msgCmd = &cli.Command{ Usage: "Print the message details", Value: true, }, + &cli.StringFlag{ + Name: "message-format", + Usage: "Format of the message (hex, base64, json)", + Value: "json", + }, &cli.BoolFlag{ Name: "exec-trace", Usage: "Print the execution trace", }, + &cli.BoolFlag{ + Name: "show-receipt", + Usage: "Print the message receipt", + Value: true, + }, &cli.BoolFlag{ Name: "gas-stats", Usage: "Print a summary of gas charges", @@ -55,125 +67,92 @@ var msgCmd = &cli.Command{ Name: "block-analysis", Usage: "Perform and print an analysis of the blocks written during message execution", }, + &cli.BoolFlag{ + Name: "dump-car", + Usage: "Dump the blocks (intermediate and final) to a CAR temp file", + }, }, Action: func(cctx *cli.Context) error { + color.Output = cctx.App.Writer + if cctx.NArg() != 1 { return lcli.IncorrectNumArgs(cctx) } - msg, err := messageFromString(cctx, cctx.Args().First()) + api, closer, err := lcli.GetFullNodeAPIV1(cctx) if err != nil { return err } + defer closer() + + ctx := lcli.ReqContext(cctx) - api, closer, err := lcli.GetFullNodeAPI(cctx) + msg, err := messageFromString(ctx, api, cctx.Args().First()) if err != nil { return err } - defer closer() - ctx := lcli.ReqContext(cctx) - - // Get the CID of the message - mcid := msg.Cid() + needTrace := cctx.Bool("exec-trace") || cctx.Bool("gas-stats") || cctx.Bool("block-analysis") || cctx.Bool("dump-car") // Search for the message on-chain - lookup, err := api.StateSearchMsg(ctx, mcid) + lookup, err := api.StateSearchMsg(ctx, types.EmptyTSK, msg.Cid(), lapi.LookbackNoLimit, true) if err != nil { return err - } - if lookup == nil { - fmt.Println("Message not found on-chain. Continuing...") - } else { - var res *lapi.InvocResult - - if cctx.Bool("exec-trace") || cctx.Bool("gas-stats") || cctx.Bool("block-analysis") { - // Re-execute the message to get the trace - executionTs, err := api.ChainGetTipSet(ctx, lookup.TipSet) - if err != nil { - return xerrors.Errorf("getting tipset: %w", err) - } - res, err = api.StateCall(ctx, lapi.NewStateCallParams(msg.VMMessage(), executionTs.Parents()).WithIncludeBlocks().ToRaw()) - if err != nil { - return xerrors.Errorf("calling message: %w", err) - } + } else if lookup == nil { + color.Red("Message not found on-chain") + } else if needTrace { + // Re-execute the message to get the trace + executionTs, err := api.ChainGetTipSet(ctx, lookup.TipSet) + if err != nil { + return xerrors.Errorf("getting tipset: %w", err) } - - if cctx.Bool("exec-trace") { - // Print the execution trace - color.Green("Execution trace:") - trace, err := json.MarshalIndent(res.ExecutionTrace, "", " ") - if err != nil { - return xerrors.Errorf("marshaling execution trace: %w", err) - } - fmt.Println(string(trace)) - fmt.Println() - - color.Green("Receipt:") - fmt.Printf("Exit code: %d\n", res.MsgRct.ExitCode) - fmt.Printf("Return: %x\n", res.MsgRct.Return) - fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed) + res, err := api.StateCall(ctx, lapi.NewStateCallParams(msg.VMMessage(), executionTs.Parents()).WithIncludeBlocks().ToRaw()) + if err != nil { + return xerrors.Errorf("executing message: %w", err) } - if cctx.Bool("block-analysis") { - if res.Blocks == nil { - return xerrors.New("no blocks found in execution trace") - } - if err := saveAndInspectBlocks(ctx, res, cctx.App.Writer); err != nil { - return err + if cctx.Bool("exec-trace") { + if err := printTrace(cctx.App.Writer, res.ExecutionTrace); err != nil { + return xerrors.Errorf("printing execution trace: %w", err) } + _, _ = fmt.Fprintln(cctx.App.Writer) } - if cctx.Bool("gas-stats") { - var printTrace func(descPfx string, trace types.ExecutionTrace) error - printTrace = func(descPfx string, trace types.ExecutionTrace) error { - typ := "Message" - if descPfx != "" { - typ = "Subcall" + if res.Blocks == nil && (cctx.Bool("dump-car") || cctx.Bool("block-analysis")) { + // could be an incompatible endpoint that doesn't serve blocks + color.Red("no blocks found in execution trace") + } else { + if cctx.Bool("dump-car") { + if err := saveBlocks(ctx, res); err != nil { + return xerrors.Errorf("writing blocks to car: %w", err) } - _, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint(fmt.Sprintf("%s (%s%s) gas charges:", typ, descPfx, trace.Msg.To))) - if err := statsTable(cctx.App.Writer, trace, false); err != nil { - return err - } - for _, subtrace := range trace.Subcalls { - _, _ = fmt.Fprintln(cctx.App.Writer) - if err := printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace); err != nil { - return err - } - } - return nil - } - if err := printTrace("", res.ExecutionTrace); err != nil { - return err - } - if len(res.ExecutionTrace.Subcalls) > 0 { _, _ = fmt.Fprintln(cctx.App.Writer) - _, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Total gas charges:")) - if err := statsTable(cctx.App.Writer, res.ExecutionTrace, true); err != nil { + } + + if cctx.Bool("block-analysis") { + if err := printBlockAnalysis(ctx, cctx.App.Writer, res); err != nil { return err } - perCallTrace := gasTracesPerCall(res.ExecutionTrace) _, _ = fmt.Fprintln(cctx.App.Writer) - _, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Gas charges per call:")) - if err := statsTable(cctx.App.Writer, perCallTrace, false); err != nil { - return err - } } } + + if cctx.Bool("gas-stats") { + if err := printGasStats(cctx.App.Writer, res.ExecutionTrace); err != nil { + return xerrors.Errorf("printing gas stats: %w", err) + } + _, _ = fmt.Fprintln(cctx.App.Writer) + } + + if cctx.Bool("show-receipt") { + printReceipt(cctx.App.Writer, res) + _, _ = fmt.Fprintln(cctx.App.Writer) + } } if cctx.Bool("show-message") { - switch msg := msg.(type) { - case *types.SignedMessage: - if err := printSignedMessage(cctx, msg); err != nil { - return err - } - case *types.Message: - if err := printMessage(cctx, msg); err != nil { - return err - } - default: - return xerrors.Errorf("this error message can't be printed") + if err := printMessage(ctx, cctx.App.Writer, api, msg, cctx.String("message-format")); err != nil { + return xerrors.Errorf("printing message: %w", err) } } @@ -181,59 +160,81 @@ var msgCmd = &cli.Command{ }, } -func printSignedMessage(cctx *cli.Context, smsg *types.SignedMessage) error { - color.Green("Signed:") - color.Blue("CID: %s\n", smsg.Cid()) +func printMessage(ctx context.Context, out io.Writer, api v1api.FullNode, msg types.ChainMsg, format string) error { + switch msg := msg.(type) { + case *types.SignedMessage: + if err := printSignedMessage(ctx, out, api, msg, format); err != nil { + return err + } + case *types.Message: + if err := printUnsignedMessage(ctx, out, api, msg, format); err != nil { + return err + } + default: + return xerrors.Errorf("this error message can't be printed") + } + return nil +} + +func printSignedMessage(ctx context.Context, out io.Writer, api v1api.FullNode, smsg *types.SignedMessage, format string) error { + _, _ = color.New(color.Bold, color.FgGreen).Println("Signed message:") + _, _ = fmt.Fprintf(out, "CID: %s\n", smsg.Cid()) b, err := smsg.Serialize() if err != nil { return err } - color.Magenta("HEX: %x\n", b) - color.Blue("B64: %s\n", base64.StdEncoding.EncodeToString(b)) - jm, err := json.MarshalIndent(smsg, "", " ") - if err != nil { - return xerrors.Errorf("marshaling as json: %w", err) + switch format { + case "hex": + _, _ = fmt.Fprintf(out, "%x\n", b) + case "base64": + _, _ = fmt.Fprintln(out, base64.StdEncoding.EncodeToString(b)) + case "json": + if jm, err := json.MarshalIndent(smsg, "", " "); err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } else { + _, _ = fmt.Println(string(jm)) + } } - color.Magenta("JSON: %s\n", string(jm)) - fmt.Println() - fmt.Println("---") - color.Green("Signed Message Details:") - fmt.Printf("Signature(hex): %x\n", smsg.Signature.Data) - fmt.Printf("Signature(b64): %s\n", base64.StdEncoding.EncodeToString(smsg.Signature.Data)) + _, _ = color.New(color.Bold, color.FgGreen).Println("\nSigned Message Details:") + _, _ = fmt.Fprintf(out, "Signature(hex): %x\n", smsg.Signature.Data) + _, _ = fmt.Fprintf(out, "Signature(b64): %s\n", base64.StdEncoding.EncodeToString(smsg.Signature.Data)) sigtype, err := smsg.Signature.Type.Name() if err != nil { sigtype = err.Error() } - fmt.Printf("Signature type: %d (%s)\n", smsg.Signature.Type, sigtype) + _, _ = fmt.Fprintf(out, "Signature type: %d (%s)\n", smsg.Signature.Type, sigtype) + _, _ = fmt.Fprintln(out) - fmt.Println("-------") - return printMessage(cctx, &smsg.Message) + return printUnsignedMessage(ctx, out, api, &smsg.Message, format) } -func printMessage(cctx *cli.Context, msg *types.Message) error { - if msg.Version != 0x6d736967 { - color.Green("Unsigned:") - color.Yellow("CID: %s\n", msg.Cid()) +func printUnsignedMessage(ctx context.Context, out io.Writer, api v1api.FullNode, msg *types.Message, format string) error { + if msg.Version != MultisigMagicNumber { + _, _ = color.New(color.Bold, color.FgGreen).Println("Unsigned message:") + _, _ = fmt.Fprintf(out, "CID: %s\n", msg.Cid()) b, err := msg.Serialize() if err != nil { return err } - color.Cyan("HEX: %x\n", b) - color.Yellow("B64: %s\n", base64.StdEncoding.EncodeToString(b)) - jm, err := json.MarshalIndent(msg, "", " ") - if err != nil { - return xerrors.Errorf("marshaling as json: %w", err) + switch format { + case "hex": + _, _ = fmt.Fprintf(out, "%x\n", b) + case "base64": + _, _ = fmt.Fprintln(out, base64.StdEncoding.EncodeToString(b)) + case "json": + if jm, err := json.MarshalIndent(msg, "", " "); err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } else { + _, _ = fmt.Println(string(jm)) + } } - - color.Cyan("JSON: %s\n", string(jm)) - fmt.Println() } else { - color.Green("Msig Propose:") + _, _ = color.New(color.Bold, color.FgGreen).Println("Msig Propose:") pp := &multisig.ProposeParams{ To: msg.To, Value: msg.Value, @@ -245,49 +246,45 @@ func printMessage(cctx *cli.Context, msg *types.Message) error { return err } - color.Cyan("HEX: %x\n", b.Bytes()) - color.Yellow("B64: %s\n", base64.StdEncoding.EncodeToString(b.Bytes())) - jm, err := json.MarshalIndent(pp, "", " ") - if err != nil { - return xerrors.Errorf("marshaling as json: %w", err) + switch format { + case "hex": + _, _ = fmt.Fprintf(out, "%x\n", b.Bytes()) + case "base64": + _, _ = fmt.Fprintln(out, base64.StdEncoding.EncodeToString(b.Bytes())) + case "json": + if jm, err := json.MarshalIndent(pp, "", " "); err != nil { + return xerrors.Errorf("marshaling as json: %w", err) + } else { + _, _ = fmt.Println(string(jm)) + } } - - color.Cyan("JSON: %s\n", string(jm)) - fmt.Println() - } - - fmt.Println("---") - color.Green("Message Details:") - fmt.Println("Value:", types.FIL(msg.Value)) - fmt.Println("Max Fees:", types.FIL(msg.RequiredFunds())) - fmt.Println("Max Total Cost:", types.FIL(big.Add(msg.RequiredFunds(), msg.Value))) - - api, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return err } - defer closer() - ctx := lcli.ReqContext(cctx) + _, _ = color.New(color.Bold, color.FgGreen).Println("\nMessage Details:") + _, _ = fmt.Fprintln(out, "Value: ", types.FIL(msg.Value)) + _, _ = fmt.Fprintln(out, "Max Fees: ", types.FIL(msg.RequiredFunds())) + _, _ = fmt.Fprintln(out, "Max Total Cost:", types.FIL(big.Add(msg.RequiredFunds(), msg.Value))) toact, err := api.StateGetActor(ctx, msg.To, types.EmptyTSK) if err != nil { + color.Red("Failed to get actor for further details: %s", err) return nil } - fmt.Println("Method:", consensus.NewActorRegistry().Methods[toact.Code][msg.Method].Name) // todo use remote - p, err := lcli.JsonParams(toact.Code, msg.Method, msg.Params) - if err != nil { + _, _ = fmt.Fprintf(out, "Method: %s (%d)\n", + color.New(color.Bold).Sprint(consensus.NewActorRegistry().Methods[toact.Code][msg.Method].Name), + msg.Method, + ) // todo use remote + + if p, err := lcli.JsonParams(toact.Code, msg.Method, msg.Params); err != nil { return err + } else { + _, _ = fmt.Fprintf(out, "Params:\n%s\n", p) } - fmt.Println("Params:", p) - - if msg, err := messageFromBytes(cctx, msg.Params); err == nil { - fmt.Println("---") - color.Red("Params message:") - - if err := printMessage(cctx, msg.VMMessage()); err != nil { + if msg, err := messageFromBytes(msg.Params); err == nil { + _, _ = color.New(color.Bold).Println("Params message:") + if err := printMessage(ctx, out, api, msg.VMMessage(), format); err != nil { return err } } @@ -295,32 +292,32 @@ func printMessage(cctx *cli.Context, msg *types.Message) error { return nil } -func messageFromString(cctx *cli.Context, smsg string) (types.ChainMsg, error) { +func messageFromString(ctx context.Context, api v1api.FullNode, smsg string) (types.ChainMsg, error) { // a CID is least likely to just decode if c, err := cid.Parse(smsg); err == nil { - return messageFromCID(cctx, c) + return messageFromCID(ctx, api, c) } // try baseX serializations next { // hex first, some hay strings may be decodable as b64 if b, err := hex.DecodeString(smsg); err == nil { - return messageFromBytes(cctx, b) + return messageFromBytes(b) } // b64 next if b, err := base64.StdEncoding.DecodeString(smsg); err == nil { - return messageFromBytes(cctx, b) + return messageFromBytes(b) } // b64u?? if b, err := base64.URLEncoding.DecodeString(smsg); err == nil { - return messageFromBytes(cctx, b) + return messageFromBytes(b) } } // maybe it's json? - if _, err := messageFromJson(cctx, []byte(smsg)); err == nil { + if _, err := messageFromJson([]byte(smsg)); err == nil { return nil, err } @@ -328,7 +325,7 @@ func messageFromString(cctx *cli.Context, smsg string) (types.ChainMsg, error) { return nil, xerrors.Errorf("couldn't decode the message") } -func messageFromJson(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { +func messageFromJson(msgb []byte) (types.ChainMsg, error) { // Unsigned { var msg types.Message @@ -352,7 +349,7 @@ func messageFromJson(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { return nil, xerrors.New("probably not a json-serialized message") } -func messageFromBytes(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { +func messageFromBytes(msgb []byte) (types.ChainMsg, error) { // Signed { var msg types.SignedMessage @@ -380,7 +377,7 @@ func messageFromBytes(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { return &types.Message{ // Hack(-ish) - Version: 0x6d736967, + Version: MultisigMagicNumber, From: i, To: pp.To, @@ -397,29 +394,21 @@ func messageFromBytes(cctx *cli.Context, msgb []byte) (types.ChainMsg, error) { // Encoded json??? { - if msg, err := messageFromJson(cctx, msgb); err == nil { + if msg, err := messageFromJson(msgb); err == nil { return msg, nil } } - return nil, xerrors.New("probably not a cbor-serialized message") + return nil, xerrors.New("couldn't decode the message") } -func messageFromCID(cctx *cli.Context, c cid.Cid) (types.ChainMsg, error) { - api, closer, err := lcli.GetFullNodeAPI(cctx) - if err != nil { - return nil, err - } - - defer closer() - ctx := lcli.ReqContext(cctx) - +func messageFromCID(ctx context.Context, api v1api.FullNode, c cid.Cid) (types.ChainMsg, error) { msgb, err := api.ChainReadObj(ctx, c) if err != nil { return nil, err } - return messageFromBytes(cctx, msgb) + return messageFromBytes(msgb) } type gasTally struct { @@ -428,6 +417,24 @@ type gasTally struct { count int } +func printTrace(out io.Writer, trace types.ExecutionTrace) error { + // Print the execution trace + _, _ = color.New(color.Bold, color.FgGreen).Println("Execution trace:") + enc := json.NewEncoder(out) + enc.SetIndent("", " ") + if err := enc.Encode(trace); err != nil { + return xerrors.Errorf("encoding execution trace: %w", err) + } + return nil +} + +func printReceipt(out io.Writer, res *api.InvocResult) { + _, _ = color.New(color.Bold, color.FgGreen).Println("Receipt:") + _, _ = fmt.Fprintf(out, "Exit code: %d\n", res.MsgRct.ExitCode) + _, _ = fmt.Fprintf(out, "Return: %x\n", res.MsgRct.Return) + _, _ = fmt.Fprintf(out, "Gas used: %d\n", res.MsgRct.GasUsed) +} + func accumGasTallies(charges map[string]*gasTally, totals *gasTally, trace types.ExecutionTrace, recurse bool) { for _, charge := range trace.GasCharges { name := charge.Name @@ -477,11 +484,11 @@ func statsTable(out io.Writer, trace types.ExecutionTrace, recurse bool) error { "Type": name, "Count": charge.count, "Storage Gas": charge.storageGas, - "S%": fmt.Sprintf("%.2f", float64(charge.storageGas)/float64(totals.storageGas)*100), + "S%": fmt.Sprintf("%.2f", safeDivide(charge.storageGas, totals.storageGas)*100), "Compute Gas": charge.computeGas, - "C%": fmt.Sprintf("%.2f", float64(charge.computeGas)/float64(totals.computeGas)*100), + "C%": fmt.Sprintf("%.2f", safeDivide(charge.computeGas, totals.computeGas)*100), "Total Gas": charge.storageGas + charge.computeGas, - "T%": fmt.Sprintf("%.2f", float64(charge.storageGas+charge.computeGas)/float64(totals.storageGas+totals.computeGas)*100), + "T%": fmt.Sprintf("%.2f", safeDivide(charge.storageGas+charge.computeGas, totals.storageGas+totals.computeGas)*100), }) } tw.Write(map[string]interface{}{ @@ -524,64 +531,77 @@ func gasTracesPerCall(inTrace types.ExecutionTrace) types.ExecutionTrace { return outTrace } -func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writer) (err error) { +func saveBlocks(ctx context.Context, res *api.InvocResult) (err error) { cachedBlocksFile := path.Join(os.TempDir(), res.MsgCid.String()+".car") bs, err := carbstore.OpenReadWrite(cachedBlocksFile, nil, carbstore.WriteAsCarV1(true)) if err != nil { - return xerrors.Errorf("opening cached blocks file: %w", err) + return xerrors.Errorf("opening blocks file: %w", err) } defer func() { if cerr := bs.Close(); cerr != nil { - err = xerrors.Errorf("closing cached blocks file: %w", cerr) + err = xerrors.Errorf("closing blocks file: %w", cerr) } }() for _, b := range res.Blocks { bc, err := blocks.NewBlockWithCid(b.Data, b.Cid) if err != nil { - return xerrors.Errorf("creating cached block: %w", err) + return xerrors.Errorf("creating block: %w", err) } if err := bs.Put(ctx, bc); err != nil { - return xerrors.Errorf("writing cached block: %w", err) + return xerrors.Errorf("writing block: %w", err) } } - color.Green("Cached blocks written to %s", cachedBlocksFile) + _, _ = color.New(color.Bold, color.FgGreen).Printf("Blocks written (final and intermediate) dumped to CAR: %s\n", cachedBlocksFile) + return nil +} + +func printBlockAnalysis(ctx context.Context, out io.Writer, res *api.InvocResult) (err error) { + _, _ = color.New(color.Bold, color.FgGreen).Println("Analysis of blocks written (final and intermediate):") + + blockMap := make(map[cid.Cid][]byte, len(res.Blocks)) + for _, b := range res.Blocks { + blockMap[b.Cid] = b.Data + } type blkStat struct { cid cid.Cid - knownType string - size int - estimatedGas int + matchingType string + size int64 + estimatedGas int64 } var explainBlocks func(descPfx string, trace types.ExecutionTrace) error explainBlocks = func(descPfx string, trace types.ExecutionTrace) error { + if trace.IpldOps == nil { + return nil + } + typ := "Message" if descPfx != "" { typ = "Subcall" } blkStats := make([]blkStat, 0, len(res.Blocks)) - var totalBytes, totalGas int + var totalBytes, totalGas int64 - for _, ll := range trace.Logs { - if strings.HasPrefix(ll, "block_link(") { - c, err := cid.Parse(strings.TrimSuffix(strings.TrimPrefix(ll, "block_link("), ")")) - if err != nil { - return xerrors.Errorf("parsing block cid: %w", err) + for _, ipldOp := range trace.IpldOps { + if ipldOp.Op == types.IpldOpPut { + if _, ok := blockMap[ipldOp.Cid]; !ok { + return xerrors.Errorf("block cid not found in execution trace: %s", ipldOp.Cid) } - blk, err := bs.Get(ctx, c) + blk, err := blocks.NewBlockWithCid(blockMap[ipldOp.Cid], ipldOp.Cid) if err != nil { - return xerrors.Errorf("getting block (%s) from cached blocks: %w", c, err) + return xerrors.Errorf("creating block: %w", err) } m, err := matchKnownBlockType(ctx, blk) if err != nil { return xerrors.Errorf("matching block type: %w", err) } - size := len(blk.RawData()) + size := int64(len(blk.RawData())) gas := 172000 + 334000 + 3340*size - blkStats = append(blkStats, blkStat{cid: c, knownType: m, size: size, estimatedGas: gas}) + blkStats = append(blkStats, blkStat{cid: ipldOp.Cid, matchingType: m, size: size, estimatedGas: gas}) totalBytes += size totalGas += gas } @@ -591,10 +611,10 @@ func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writ return nil } - _, _ = fmt.Fprintln(out, color.New(color.Bold).Sprint(fmt.Sprintf("%s (%s%s) block writes:", typ, descPfx, trace.Msg.To))) + _, _ = color.New(color.Bold).Printf("%s (%s%s) block writes:\n", typ, descPfx, trace.Msg.To) tw := tablewriter.New( tablewriter.Col("CID"), - tablewriter.Col("Known Type"), + tablewriter.Col("Matching Type"), tablewriter.Col("Size", tablewriter.RightAlign()), tablewriter.Col("S%", tablewriter.RightAlign()), tablewriter.Col("Estimated Gas", tablewriter.RightAlign()), @@ -603,16 +623,16 @@ func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writ for _, bs := range blkStats { tw.Write(map[string]interface{}{ "CID": bs.cid, - "Known Type": bs.knownType, + "Matching Type": bs.matchingType, "Size": bs.size, - "S%": fmt.Sprintf("%.2f", float64(bs.size)/float64(totalBytes)*100), + "S%": fmt.Sprintf("%.2f", safeDivide(bs.size, totalBytes)*100), "Estimated Gas": bs.estimatedGas, - "G%": fmt.Sprintf("%.2f", float64(bs.estimatedGas)/float64(totalGas)*100), + "G%": fmt.Sprintf("%.2f", safeDivide(bs.estimatedGas, totalGas)*100), }) } tw.Write(map[string]interface{}{ "CID": "Total", - "Known Type": "", + "Matching Type": "", "Size": totalBytes, "S%": "100.00", "Estimated Gas": totalGas, @@ -634,3 +654,47 @@ func saveAndInspectBlocks(ctx context.Context, res *api.InvocResult, out io.Writ } return } + +func printGasStats(out io.Writer, trace types.ExecutionTrace) error { + _, _ = color.New(color.Bold, color.FgGreen).Println("Gas charges:") + + var printTrace func(descPfx string, trace types.ExecutionTrace) error + printTrace = func(descPfx string, trace types.ExecutionTrace) error { + typ := "Message" + if descPfx != "" { + typ = "Subcall" + } + _, _ = color.New(color.Bold).Printf("%s (%s%s) gas charges:\n", typ, descPfx, trace.Msg.To) + if err := statsTable(out, trace, false); err != nil { + return err + } + for _, subtrace := range trace.Subcalls { + if err := printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace); err != nil { + return err + } + } + return nil + } + if err := printTrace("", trace); err != nil { + return err + } + if len(trace.Subcalls) > 0 { + _, _ = color.New(color.Bold).Println("Total gas charges:") + if err := statsTable(out, trace, true); err != nil { + return err + } + perCallTrace := gasTracesPerCall(trace) + _, _ = color.New(color.Bold).Println("Gas charges per call:") + if err := statsTable(out, perCallTrace, false); err != nil { + return err + } + } + return nil +} + +func safeDivide(a, b int64) float64 { + if b == 0 { + return 0 + } + return float64(a) / float64(b) +} diff --git a/documentation/en/api-v0-methods.md b/documentation/en/api-v0-methods.md index e65018eaa85..2c06e2db973 100644 --- a/documentation/en/api-v0-methods.md +++ b/documentation/en/api-v0-methods.md @@ -3726,29 +3726,7 @@ Perms: read Inputs: ```json [ - { - "Version": 42, - "To": "f01234", - "From": "f01234", - "Nonce": 42, - "Value": "0", - "GasLimit": 9, - "GasFeeCap": "0", - "GasPremium": "0", - "Method": 1, - "Params": "Ynl0ZSBhcnJheQ==", - "CID": { - "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" - } - }, - [ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" - } - ] + "Bw==" ] ``` @@ -3875,16 +3853,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -4151,16 +4147,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -5632,16 +5646,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 5f4897a37f0..cbe4f2aa65f 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -6006,29 +6006,7 @@ Perms: read Inputs: ```json [ - { - "Version": 42, - "To": "f01234", - "From": "f01234", - "Nonce": 42, - "Value": "0", - "GasLimit": 9, - "GasFeeCap": "0", - "GasPremium": "0", - "Method": 1, - "Params": "Ynl0ZSBhcnJheQ==", - "CID": { - "/": "bafy2bzacebbpdegvr3i4cosewthysg5xkxpqfn2wfcz6mv2hmoktwbdxkax4s" - } - }, - [ - { - "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" - }, - { - "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" - } - ] + "Bw==" ] ``` @@ -6155,16 +6133,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -6431,16 +6427,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" @@ -8133,16 +8147,34 @@ Response: "Subcalls": null, "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] } ], "Logs": [ "string value" + ], + "IpldOps": [ + { + "Op": "Get", + "Cid": { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + "Size": 42 + } ] }, "Error": "string value", "Duration": 60000000000, - "CachedBlocks": [ + "Blocks": [ { "Cid": { "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 285faf5ab2b..444583a08ac 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 285faf5ab2b6e6a219b7fe2114e54c5a45fce3cf +Subproject commit 444583a08ac52739f2523f8c59f4ad91156ba2e6 diff --git a/gen/main.go b/gen/main.go index 261a85bcb00..0197989c1d8 100644 --- a/gen/main.go +++ b/gen/main.go @@ -175,5 +175,6 @@ func generateChainTypes() error { types.MessageTrace{}, types.ReturnTrace{}, types.ExecutionTrace{}, + types.TraceIpld{}, ) }