diff --git a/api/api_full.go b/api/api_full.go index f919bc13b..2449b1df4 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -874,6 +874,11 @@ type FullNode interface { // Replays all transactions in a block returning the requested traces for each transaction EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) //perm:read + // go-ethereum "debug" trace endpoints + EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) + EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) + EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) + // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus daemon is running with the // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that diff --git a/api/api_gateway.go b/api/api_gateway.go index 27e725457..12842654a 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -129,4 +129,7 @@ type Gateway interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) + EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) + EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) } diff --git a/api/eth_aliases.go b/api/eth_aliases.go index eb0c51005..3c194d124 100644 --- a/api/eth_aliases.go +++ b/api/eth_aliases.go @@ -43,6 +43,10 @@ func CreateEthRPCAliases(as apitypes.Aliaser) { as.AliasMethod("trace_block", "Filecoin.EthTraceBlock") as.AliasMethod("trace_replayBlockTransactions", "Filecoin.EthTraceReplayBlockTransactions") + as.AliasMethod("debug_traceCall", "Filecoin.EthDebugTraceCall") + as.AliasMethod("debug_traceBlockByHash", "Filecoin.EthDebugTraceBlockByHash") + as.AliasMethod("debug_traceBlockByNumber", "Filecoin.EthDebugTraceBlockByNumber") + as.AliasMethod("net_version", "Filecoin.NetVersion") as.AliasMethod("net_listening", "Filecoin.NetListening") diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 856d83813..f347cc5b2 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -1500,12 +1500,57 @@ func (m *MockFullNode) EthTraceBlock(arg0 context.Context, arg1 string) ([]*etht return ret0, ret1 } +// EthDebugTraceCall mocks base method. +func (m *MockFullNode) EthDebugTraceCall(ctx context.Context, arg0 ethtypes.EthTxArgs, arg1 ethtypes.EthBlockNumberOrHash, arg2 *ethtypes.TraceCallConfig) (interface{}, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthDebugTraceCall", arg0, arg1, arg2) + ret0, _ := ret[0].(interface{}) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthDebugTraceBlockByNumber mocks base method. +func (m *MockFullNode) EthDebugTraceBlockByNumber(ctx context.Context, arg0 string, arg1 *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthDebugTraceBlockByNumber", arg0, arg1) + ret0, _ := ret[0].([]*ethtypes.TxTraceResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthDebugTraceBlockByHash mocks base method. +func (m *MockFullNode) EthDebugTraceBlockByHash(ctx context.Context, arg0 ethtypes.EthHash, arg1 *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthDebugTraceBlockByHash", arg0, arg1) + ret0, _ := ret[0].([]*ethtypes.TxTraceResult) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + // EthTraceBlock indicates an expected call of EthTraceBlock. func (mr *MockFullNodeMockRecorder) EthTraceBlock(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceBlock", reflect.TypeOf((*MockFullNode)(nil).EthTraceBlock), arg0, arg1) } +// EthDebugTraceCall indicates an expected of EthDebugTraceCall +func (mr *MockFullNodeMockRecorder) EthDebugTraceCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthDebugTraceCall", reflect.TypeOf((*MockFullNode)(nil).EthDebugTraceCall), arg0, arg1, arg2) +} + +// EthDebugTraceBlockByHash indicates an expected of EthDebugTraceBlockByHash +func (mr *MockFullNodeMockRecorder) EthDebugTraceBlockByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthDebugTraceBlockByHash", reflect.TypeOf((*MockFullNode)(nil).EthDebugTraceBlockByHash), arg0, arg1) +} + +// EthDebugTraceBlockByNumber indicates an expected of EthDebugTraceBlockByHash +func (mr *MockFullNodeMockRecorder) EthDebugTraceBlockByNumber(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthDebugTraceBlockByNumber", reflect.TypeOf((*MockFullNode)(nil).EthDebugTraceBlockByNumber), arg0, arg1) +} + // EthTraceReplayBlockTransactions mocks base method. func (m *MockFullNode) EthTraceReplayBlockTransactions(arg0 context.Context, arg1 string, arg2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) { m.ctrl.T.Helper() diff --git a/chain/types/ethtypes/eth_types.go b/chain/types/ethtypes/eth_types.go index b796e6f56..deff2a8a1 100644 --- a/chain/types/ethtypes/eth_types.go +++ b/chain/types/ethtypes/eth_types.go @@ -102,6 +102,10 @@ func (e EthBigInt) MarshalJSON() ([]byte, error) { return json.Marshal(e.String()) } +func (e EthBigInt) ToInt() big.Int { + return (big.Int)(e) +} + func (e *EthBigInt) UnmarshalJSON(b []byte) error { var s string if err := json.Unmarshal(b, &s); err != nil { diff --git a/chain/types/ethtypes/tracing.go b/chain/types/ethtypes/tracing.go new file mode 100644 index 000000000..2c1fd3378 --- /dev/null +++ b/chain/types/ethtypes/tracing.go @@ -0,0 +1,120 @@ +package ethtypes + +import ( + "encoding/json" + "math/big" + + logger "github.com/ipfs/go-log/v2" +) + +// TraceConfig holds extra parameters to trace functions. +type TraceConfig struct { + *logger.Config + Tracer *string + Timeout *string + Reexec *uint64 + // Config specific to given tracer. Note struct logger + // config are historically embedded in main object. + TracerConfig json.RawMessage +} + +// TraceCallConfig is the config for traceCall API. It holds one more +// field to override the state for tracing. +type TraceCallConfig struct { + TraceConfig + StateOverrides *StateOverride + BlockOverrides *BlockOverrides +} + +// OverrideAccount indicates the overriding fields of account during the execution +// of a message call. +// Note, state and stateDiff can't be specified at the same time. If state is +// set, message execution will only use the data in the given state. Otherwise +// if statDiff is set, all diff will be applied first and then execute the call +// message. +type OverrideAccount struct { + Nonce *EthUint64 `json:"nonce"` + Code *EthBytes `json:"code"` + Balance **EthBigInt `json:"balance"` + State *map[EthHash]EthHash `json:"state"` + StateDiff *map[EthHash]EthHash `json:"stateDiff"` +} + +// StateOverride is the collection of overridden accounts. +type StateOverride map[EthHash]OverrideAccount + +// BlockOverrides is a set of header fields to override. +type BlockOverrides struct { + Number *EthBigInt + Difficulty *EthBigInt + Time *EthUint64 + GasLimit *EthUint64 + Coinbase *EthAddress + Random *EthHash + BaseFee *EthUint64 +} + +// Apply overrides the given header fields into the given block context. +func (diff *BlockOverrides) Apply(blockCtx *BlockContext) { + if diff == nil { + return + } + if diff.Number != nil { + blockCtx.BlockNumber = diff.Number.Int + } + if diff.Difficulty != nil { + blockCtx.Difficulty = diff.Difficulty.Int + } + if diff.Time != nil { + blockCtx.Time = uint64(*diff.Time) + } + if diff.GasLimit != nil { + blockCtx.GasLimit = uint64(*diff.GasLimit) + } + if diff.Coinbase != nil { + blockCtx.Coinbase = *diff.Coinbase + } + if diff.Random != nil { + blockCtx.Random = diff.Random + } + if diff.BaseFee != nil { + blockCtx.BaseFee = new(big.Int).SetUint64((uint64)(*diff.BaseFee)) + } +} + +type ( + // CanTransferFunc is the signature of a transfer guard function + CanTransferFunc func(StateDB, EthAddress, *big.Int) bool + // TransferFunc is the signature of a transfer function + TransferFunc func(StateDB, EthAddress, EthAddress, *big.Int) + // GetHashFunc returns the n'th block hash in the blockchain + // and is used by the BLOCKHASH EVM op code. + GetHashFunc func(uint64) EthAddress +) + +// BlockContext provides the EVM with auxiliary information. Once provided +// it shouldn't be modified. +type BlockContext struct { + // CanTransfer returns whether the account contains + // sufficient ether to transfer the value + CanTransfer CanTransferFunc + // Transfer transfers ether from one account to the other + Transfer TransferFunc + // GetHash returns the hash corresponding to n + GetHash GetHashFunc + + // Block information + Coinbase EthAddress // Provides information for COINBASE + GasLimit uint64 // Provides information for GASLIMIT + BlockNumber *big.Int // Provides information for NUMBER + Time uint64 // Provides information for TIME + Difficulty *big.Int // Provides information for DIFFICULTY + BaseFee *big.Int // Provides information for BASEFEE + Random *EthHash // Provides information for PREVRANDAO +} + +// TxTraceResult is the result of a single transaction trace. +type TxTraceResult struct { + Result interface{} `json:"result,omitempty"` // Trace results produced by the tracer + Error string `json:"error,omitempty"` // Trace failure produced by the tracer +} diff --git a/gateway/node.go b/gateway/node.go index 367e645c1..a400d8909 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -146,6 +146,9 @@ type TargetAPI interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) + EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) + EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) } var _ TargetAPI = *new(api.FullNode) // gateway depends on latest diff --git a/gateway/proxy_eth.go b/gateway/proxy_eth.go index e6d433a17..d6562625b 100644 --- a/gateway/proxy_eth.go +++ b/gateway/proxy_eth.go @@ -606,6 +606,42 @@ func (gw *Node) EthTraceReplayBlockTransactions(ctx context.Context, blkNum stri return gw.target.EthTraceReplayBlockTransactions(ctx, blkNum, traceTypes) } +func (gw *Node) EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkEthBlockParam(ctx, blockNrOrHash, 0); err != nil { + return nil, err + } + + return gw.target.EthDebugTraceCall(ctx, args, blockNrOrHash, config) +} + +func (gw *Node) EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkBlkHash(ctx, hash); err != nil { + return nil, err + } + + return gw.target.EthDebugTraceBlockByHash(ctx, hash, config) +} + +func (gw *Node) EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + if err := gw.limit(ctx, stateRateLimitTokens); err != nil { + return nil, err + } + + if err := gw.checkBlkParam(ctx, number, 0); err != nil { + return nil, err + } + + return gw.target.EthDebugTraceBlockByNumber(ctx, number, config) +} + var EthMaxFiltersPerConn = 16 // todo make this configurable func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) { diff --git a/node/impl/full/dummy.go b/node/impl/full/dummy.go index 743eadf34..dae967ee4 100644 --- a/node/impl/full/dummy.go +++ b/node/impl/full/dummy.go @@ -186,5 +186,17 @@ func (e *EthModuleDummy) EthTraceReplayBlockTransactions(ctx context.Context, bl return nil, ErrModuleDisabled } +func (e *EthModuleDummy) EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + return nil, ErrModuleDisabled +} + +func (e *EthModuleDummy) EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) { + return nil, ErrModuleDisabled +} + var _ EthModuleAPI = &EthModuleDummy{} var _ EthEventAPI = &EthModuleDummy{} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 6b8b0e0aa..157f204c0 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -73,6 +73,9 @@ type EthModuleAPI interface { Web3ClientVersion(ctx context.Context) (string, error) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) + EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) + EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) + EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) } type EthEventAPI interface { @@ -969,6 +972,18 @@ func (a *EthModule) EthTraceReplayBlockTransactions(ctx context.Context, blkNum return allTraces, nil } +func (a *EthModule) EthDebugTraceCall(ctx context.Context, args ethtypes.EthTxArgs, blockNrOrHash ethtypes.EthBlockNumberOrHash, config *ethtypes.TraceCallConfig) (interface{}, error) { + panic("implement me") +} + +func (a *EthModule) EthDebugTraceBlockByNumber(ctx context.Context, number string, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + panic("implement me") +} + +func (a *EthModule) EthDebugTraceBlockByHash(ctx context.Context, hash ethtypes.EthHash, config *ethtypes.TraceConfig) ([]*ethtypes.TxTraceResult, error) { + panic("implement me") +} + func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk types.TipSetKey) (res *api.InvocResult, err error) { ts, err := a.Chain.GetTipSetFromKey(ctx, tsk) if err != nil {