Merge pull request #11100 from filecoin-project/traceapi
Add new tracing API
This commit is contained in:
commit
d71d647aaf
@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
# UNRELEASED
|
# UNRELEASED
|
||||||
|
|
||||||
|
## New features
|
||||||
|
- feat: Added new tracing API (**HIGHLY EXPERIMENTAL**) supporting two RPC methods: `trace_block` and `trace_replayBlockTransactions` ([filecoin-project/lotus#11100](https://github.com/filecoin-project/lotus/pull/11100))
|
||||||
|
|
||||||
# v1.23.3 / 2023-08-01
|
# v1.23.3 / 2023-08-01
|
||||||
|
|
||||||
This feature release of Lotus includes numerous improvements and enhancements for node operators, ETH RPC-providers and storage providers.
|
This feature release of Lotus includes numerous improvements and enhancements for node operators, ETH RPC-providers and storage providers.
|
||||||
|
@ -868,6 +868,13 @@ type FullNode interface {
|
|||||||
// Returns the client version
|
// Returns the client version
|
||||||
Web3ClientVersion(ctx context.Context) (string, error) //perm:read
|
Web3ClientVersion(ctx context.Context) (string, error) //perm:read
|
||||||
|
|
||||||
|
// TraceAPI related methods
|
||||||
|
//
|
||||||
|
// Returns traces created at given block
|
||||||
|
EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) //perm:read
|
||||||
|
// 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
|
||||||
|
|
||||||
// CreateBackup creates node backup onder the specified file name. The
|
// CreateBackup creates node backup onder the specified file name. The
|
||||||
// method requires that the lotus daemon is running with the
|
// method requires that the lotus daemon is running with the
|
||||||
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
||||||
|
@ -127,4 +127,6 @@ type Gateway interface {
|
|||||||
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
||||||
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
||||||
Web3ClientVersion(ctx context.Context) (string, error)
|
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)
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,9 @@ func CreateEthRPCAliases(as apitypes.Aliaser) {
|
|||||||
as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe")
|
as.AliasMethod("eth_subscribe", "Filecoin.EthSubscribe")
|
||||||
as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe")
|
as.AliasMethod("eth_unsubscribe", "Filecoin.EthUnsubscribe")
|
||||||
|
|
||||||
|
as.AliasMethod("trace_block", "Filecoin.EthTraceBlock")
|
||||||
|
as.AliasMethod("trace_replayBlockTransactions", "Filecoin.EthTraceReplayBlockTransactions")
|
||||||
|
|
||||||
as.AliasMethod("net_version", "Filecoin.NetVersion")
|
as.AliasMethod("net_version", "Filecoin.NetVersion")
|
||||||
as.AliasMethod("net_listening", "Filecoin.NetListening")
|
as.AliasMethod("net_listening", "Filecoin.NetListening")
|
||||||
|
|
||||||
|
@ -1491,6 +1491,36 @@ func (mr *MockFullNodeMockRecorder) EthSyncing(arg0 interface{}) *gomock.Call {
|
|||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSyncing", reflect.TypeOf((*MockFullNode)(nil).EthSyncing), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSyncing", reflect.TypeOf((*MockFullNode)(nil).EthSyncing), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EthTraceBlock mocks base method.
|
||||||
|
func (m *MockFullNode) EthTraceBlock(arg0 context.Context, arg1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "EthTraceBlock", arg0, arg1)
|
||||||
|
ret0, _ := ret[0].([]*ethtypes.EthTraceBlock)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthTraceReplayBlockTransactions mocks base method.
|
||||||
|
func (m *MockFullNode) EthTraceReplayBlockTransactions(arg0 context.Context, arg1 string, arg2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "EthTraceReplayBlockTransactions", arg0, arg1, arg2)
|
||||||
|
ret0, _ := ret[0].([]*ethtypes.EthTraceReplayBlockTransaction)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// EthTraceReplayBlockTransactions indicates an expected call of EthTraceReplayBlockTransactions.
|
||||||
|
func (mr *MockFullNodeMockRecorder) EthTraceReplayBlockTransactions(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthTraceReplayBlockTransactions", reflect.TypeOf((*MockFullNode)(nil).EthTraceReplayBlockTransactions), arg0, arg1, arg2)
|
||||||
|
}
|
||||||
|
|
||||||
// EthUninstallFilter mocks base method.
|
// EthUninstallFilter mocks base method.
|
||||||
func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) {
|
func (m *MockFullNode) EthUninstallFilter(arg0 context.Context, arg1 ethtypes.EthFilterID) (bool, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
|
@ -316,6 +316,10 @@ type FullNodeMethods struct {
|
|||||||
|
|
||||||
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `perm:"read"`
|
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) `perm:"read"`
|
||||||
|
|
||||||
|
EthTraceBlock func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) `perm:"read"`
|
||||||
|
|
||||||
|
EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) `perm:"read"`
|
||||||
|
|
||||||
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"`
|
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) `perm:"read"`
|
||||||
|
|
||||||
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"`
|
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) `perm:"read"`
|
||||||
@ -732,6 +736,10 @@ type GatewayMethods struct {
|
|||||||
|
|
||||||
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) ``
|
EthSyncing func(p0 context.Context) (ethtypes.EthSyncingResult, error) ``
|
||||||
|
|
||||||
|
EthTraceBlock func(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) ``
|
||||||
|
|
||||||
|
EthTraceReplayBlockTransactions func(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) ``
|
||||||
|
|
||||||
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) ``
|
EthUninstallFilter func(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) ``
|
||||||
|
|
||||||
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) ``
|
EthUnsubscribe func(p0 context.Context, p1 ethtypes.EthSubscriptionID) (bool, error) ``
|
||||||
@ -2457,6 +2465,28 @@ func (s *FullNodeStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult
|
|||||||
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStruct) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
if s.Internal.EthTraceBlock == nil {
|
||||||
|
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||||
|
}
|
||||||
|
return s.Internal.EthTraceBlock(p0, p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStub) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStruct) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
if s.Internal.EthTraceReplayBlockTransactions == nil {
|
||||||
|
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||||
|
}
|
||||||
|
return s.Internal.EthTraceReplayBlockTransactions(p0, p1, p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
func (s *FullNodeStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
||||||
if s.Internal.EthUninstallFilter == nil {
|
if s.Internal.EthUninstallFilter == nil {
|
||||||
return false, ErrNotSupported
|
return false, ErrNotSupported
|
||||||
@ -4679,6 +4709,28 @@ func (s *GatewayStub) EthSyncing(p0 context.Context) (ethtypes.EthSyncingResult,
|
|||||||
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
return *new(ethtypes.EthSyncingResult), ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *GatewayStruct) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
if s.Internal.EthTraceBlock == nil {
|
||||||
|
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||||
|
}
|
||||||
|
return s.Internal.EthTraceBlock(p0, p1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GatewayStub) EthTraceBlock(p0 context.Context, p1 string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
return *new([]*ethtypes.EthTraceBlock), ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GatewayStruct) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
if s.Internal.EthTraceReplayBlockTransactions == nil {
|
||||||
|
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||||
|
}
|
||||||
|
return s.Internal.EthTraceReplayBlockTransactions(p0, p1, p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *GatewayStub) EthTraceReplayBlockTransactions(p0 context.Context, p1 string, p2 []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
return *new([]*ethtypes.EthTraceReplayBlockTransaction), ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
func (s *GatewayStruct) EthUninstallFilter(p0 context.Context, p1 ethtypes.EthFilterID) (bool, error) {
|
||||||
if s.Internal.EthUninstallFilter == nil {
|
if s.Internal.EthUninstallFilter == nil {
|
||||||
return false, ErrNotSupported
|
return false, ErrNotSupported
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -2289,7 +2289,7 @@ func (t *GasTrace) UnmarshalCBOR(r io.Reader) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var lengthBufMessageTrace = []byte{134}
|
var lengthBufMessageTrace = []byte{137}
|
||||||
|
|
||||||
func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
|
func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
@ -2343,6 +2343,23 @@ func (t *MessageTrace) MarshalCBOR(w io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// t.GasLimit (uint64) (uint64)
|
||||||
|
|
||||||
|
if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.GasLimit)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.ReadOnly (bool) (bool)
|
||||||
|
if err := cbg.WriteBool(w, t.ReadOnly); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// t.CodeCid (cid.Cid) (struct)
|
||||||
|
|
||||||
|
if err := cbg.WriteCid(cw, t.CodeCid); err != nil {
|
||||||
|
return xerrors.Errorf("failed to write cid field t.CodeCid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2365,7 +2382,7 @@ func (t *MessageTrace) UnmarshalCBOR(r io.Reader) (err error) {
|
|||||||
return fmt.Errorf("cbor input should be of type array")
|
return fmt.Errorf("cbor input should be of type array")
|
||||||
}
|
}
|
||||||
|
|
||||||
if extra != 6 {
|
if extra != 9 {
|
||||||
return fmt.Errorf("cbor input had wrong number of fields")
|
return fmt.Errorf("cbor input had wrong number of fields")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2444,6 +2461,49 @@ func (t *MessageTrace) UnmarshalCBOR(r io.Reader) (err error) {
|
|||||||
}
|
}
|
||||||
t.ParamsCodec = uint64(extra)
|
t.ParamsCodec = uint64(extra)
|
||||||
|
|
||||||
|
}
|
||||||
|
// t.GasLimit (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.GasLimit = uint64(extra)
|
||||||
|
|
||||||
|
}
|
||||||
|
// t.ReadOnly (bool) (bool)
|
||||||
|
|
||||||
|
maj, extra, err = cr.ReadHeader()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if maj != cbg.MajOther {
|
||||||
|
return fmt.Errorf("booleans must be major type 7")
|
||||||
|
}
|
||||||
|
switch extra {
|
||||||
|
case 20:
|
||||||
|
t.ReadOnly = false
|
||||||
|
case 21:
|
||||||
|
t.ReadOnly = true
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("booleans are either major type 7, value 20 or 21 (got %d)", extra)
|
||||||
|
}
|
||||||
|
// t.CodeCid (cid.Cid) (struct)
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
c, err := cbg.ReadCid(cr)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("failed to read cid field t.CodeCid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.CodeCid = c
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
"github.com/filecoin-project/go-state-types/big"
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||||
|
|
||||||
@ -929,3 +930,57 @@ func (e *EthBlockNumberOrHash) UnmarshalJSON(b []byte) error {
|
|||||||
|
|
||||||
return errors.New("invalid block param")
|
return errors.New("invalid block param")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EthTrace struct {
|
||||||
|
Action EthTraceAction `json:"action"`
|
||||||
|
Result EthTraceResult `json:"result"`
|
||||||
|
Subtraces int `json:"subtraces"`
|
||||||
|
TraceAddress []int `json:"traceAddress"`
|
||||||
|
Type string `json:"Type"`
|
||||||
|
|
||||||
|
Parent *EthTrace `json:"-"`
|
||||||
|
|
||||||
|
// if a subtrace makes a call to GetBytecode, we store a pointer to that subtrace here
|
||||||
|
// which we then lookup when checking for delegatecall (InvokeContractDelegate)
|
||||||
|
LastByteCode *EthTrace `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *EthTrace) SetCallType(callType string) {
|
||||||
|
t.Action.CallType = callType
|
||||||
|
t.Type = callType
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthTraceBlock struct {
|
||||||
|
*EthTrace
|
||||||
|
BlockHash EthHash `json:"blockHash"`
|
||||||
|
BlockNumber int64 `json:"blockNumber"`
|
||||||
|
TransactionHash EthHash `json:"transactionHash"`
|
||||||
|
TransactionPosition int `json:"transactionPosition"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthTraceReplayBlockTransaction struct {
|
||||||
|
Output EthBytes `json:"output"`
|
||||||
|
StateDiff *string `json:"stateDiff"`
|
||||||
|
Trace []*EthTrace `json:"trace"`
|
||||||
|
TransactionHash EthHash `json:"transactionHash"`
|
||||||
|
VmTrace *string `json:"vmTrace"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthTraceAction struct {
|
||||||
|
CallType string `json:"callType"`
|
||||||
|
From EthAddress `json:"from"`
|
||||||
|
To EthAddress `json:"to"`
|
||||||
|
Gas EthUint64 `json:"gas"`
|
||||||
|
Input EthBytes `json:"input"`
|
||||||
|
Value EthBigInt `json:"value"`
|
||||||
|
|
||||||
|
FilecoinMethod abi.MethodNum `json:"-"`
|
||||||
|
FilecoinCodeCid cid.Cid `json:"-"`
|
||||||
|
FilecoinFrom address.Address `json:"-"`
|
||||||
|
FilecoinTo address.Address `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthTraceResult struct {
|
||||||
|
GasUsed EthUint64 `json:"gasUsed"`
|
||||||
|
Output EthBytes `json:"output"`
|
||||||
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
"github.com/filecoin-project/go-state-types/exitcode"
|
"github.com/filecoin-project/go-state-types/exitcode"
|
||||||
@ -24,6 +26,9 @@ type MessageTrace struct {
|
|||||||
Method abi.MethodNum
|
Method abi.MethodNum
|
||||||
Params []byte
|
Params []byte
|
||||||
ParamsCodec uint64
|
ParamsCodec uint64
|
||||||
|
GasLimit uint64
|
||||||
|
ReadOnly bool
|
||||||
|
CodeCid cid.Cid
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReturnTrace struct {
|
type ReturnTrace struct {
|
||||||
|
@ -4873,7 +4873,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -4897,7 +4902,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -5103,7 +5113,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -5127,7 +5142,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -6493,7 +6513,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -6517,7 +6542,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
|
@ -104,6 +104,8 @@
|
|||||||
* [EthSendRawTransaction](#EthSendRawTransaction)
|
* [EthSendRawTransaction](#EthSendRawTransaction)
|
||||||
* [EthSubscribe](#EthSubscribe)
|
* [EthSubscribe](#EthSubscribe)
|
||||||
* [EthSyncing](#EthSyncing)
|
* [EthSyncing](#EthSyncing)
|
||||||
|
* [EthTraceBlock](#EthTraceBlock)
|
||||||
|
* [EthTraceReplayBlockTransactions](#EthTraceReplayBlockTransactions)
|
||||||
* [EthUninstallFilter](#EthUninstallFilter)
|
* [EthUninstallFilter](#EthUninstallFilter)
|
||||||
* [EthUnsubscribe](#EthUnsubscribe)
|
* [EthUnsubscribe](#EthUnsubscribe)
|
||||||
* [Filecoin](#Filecoin)
|
* [Filecoin](#Filecoin)
|
||||||
@ -3083,6 +3085,99 @@ Inputs: `null`
|
|||||||
|
|
||||||
Response: `false`
|
Response: `false`
|
||||||
|
|
||||||
|
### EthTraceBlock
|
||||||
|
TraceAPI related methods
|
||||||
|
|
||||||
|
Returns traces created at given block
|
||||||
|
|
||||||
|
|
||||||
|
Perms: read
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"string value"
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"action": {
|
||||||
|
"callType": "string value",
|
||||||
|
"from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||||
|
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||||
|
"gas": "0x5",
|
||||||
|
"input": "0x07",
|
||||||
|
"value": "0x0"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"gasUsed": "0x5",
|
||||||
|
"output": "0x07"
|
||||||
|
},
|
||||||
|
"subtraces": 123,
|
||||||
|
"traceAddress": [
|
||||||
|
123
|
||||||
|
],
|
||||||
|
"Type": "string value",
|
||||||
|
"blockHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||||
|
"blockNumber": 9,
|
||||||
|
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||||
|
"transactionPosition": 123
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### EthTraceReplayBlockTransactions
|
||||||
|
Replays all transactions in a block returning the requested traces for each transaction
|
||||||
|
|
||||||
|
|
||||||
|
Perms: read
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
"string value",
|
||||||
|
[
|
||||||
|
"string value"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"output": "0x07",
|
||||||
|
"stateDiff": "string value",
|
||||||
|
"trace": [
|
||||||
|
{
|
||||||
|
"action": {
|
||||||
|
"callType": "string value",
|
||||||
|
"from": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||||
|
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
|
||||||
|
"gas": "0x5",
|
||||||
|
"input": "0x07",
|
||||||
|
"value": "0x0"
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"gasUsed": "0x5",
|
||||||
|
"output": "0x07"
|
||||||
|
},
|
||||||
|
"subtraces": 123,
|
||||||
|
"traceAddress": [
|
||||||
|
123
|
||||||
|
],
|
||||||
|
"Type": "string value"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"transactionHash": "0x37690cfec6c1bf4c3b9288c7a5d783e98731e90b0a4c177c2a374c7a9427355e",
|
||||||
|
"vmTrace": "string value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
### EthUninstallFilter
|
### EthUninstallFilter
|
||||||
Uninstalls a filter with given id.
|
Uninstalls a filter with given id.
|
||||||
|
|
||||||
@ -6312,7 +6407,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -6336,7 +6436,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -6542,7 +6647,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -6566,7 +6676,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -8061,7 +8176,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
@ -8085,7 +8205,12 @@ Response:
|
|||||||
"Value": "0",
|
"Value": "0",
|
||||||
"Method": 1,
|
"Method": 1,
|
||||||
"Params": "Ynl0ZSBhcnJheQ==",
|
"Params": "Ynl0ZSBhcnJheQ==",
|
||||||
"ParamsCodec": 42
|
"ParamsCodec": 42,
|
||||||
|
"GasLimit": 42,
|
||||||
|
"ReadOnly": true,
|
||||||
|
"CodeCid": {
|
||||||
|
"/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"MsgRct": {
|
"MsgRct": {
|
||||||
"ExitCode": 0,
|
"ExitCode": 0,
|
||||||
|
2
extern/filecoin-ffi
vendored
2
extern/filecoin-ffi
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 8c2147f706a8ccdbd27b7340c87dc5953448c911
|
Subproject commit bf5edd551d23901fa565aac4ce94433afe0c278e
|
@ -144,6 +144,8 @@ type TargetAPI interface {
|
|||||||
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
EthSubscribe(ctx context.Context, params jsonrpc.RawParams) (ethtypes.EthSubscriptionID, error)
|
||||||
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error)
|
||||||
Web3ClientVersion(ctx context.Context) (string, error)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ TargetAPI = *new(api.FullNode) // gateway depends on latest
|
var _ TargetAPI = *new(api.FullNode) // gateway depends on latest
|
||||||
|
@ -21,14 +21,6 @@ import (
|
|||||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (gw *Node) Web3ClientVersion(ctx context.Context) (string, error) {
|
|
||||||
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gw.target.Web3ClientVersion(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (gw *Node) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) {
|
func (gw *Node) EthAccounts(ctx context.Context) ([]ethtypes.EthAddress, error) {
|
||||||
// gateway provides public API, so it can't hold user accounts
|
// gateway provides public API, so it can't hold user accounts
|
||||||
return []ethtypes.EthAddress{}, nil
|
return []ethtypes.EthAddress{}, nil
|
||||||
@ -582,6 +574,38 @@ func (gw *Node) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionI
|
|||||||
return ok, nil
|
return ok, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gw *Node) Web3ClientVersion(ctx context.Context) (string, error) {
|
||||||
|
if err := gw.limit(ctx, basicRateLimitTokens); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gw.target.Web3ClientVersion(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gw *Node) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gw.target.EthTraceBlock(ctx, blkNum)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gw *Node) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
if err := gw.limit(ctx, stateRateLimitTokens); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gw.checkBlkParam(ctx, blkNum, 0); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gw.target.EthTraceReplayBlockTransactions(ctx, blkNum, traceTypes)
|
||||||
|
}
|
||||||
|
|
||||||
var EthMaxFiltersPerConn = 16 // todo make this configurable
|
var EthMaxFiltersPerConn = 16 // todo make this configurable
|
||||||
|
|
||||||
func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) {
|
func addUserFilterLimited(ctx context.Context, cb func() (ethtypes.EthFilterID, error)) (ethtypes.EthFilterID, error) {
|
||||||
|
@ -178,5 +178,13 @@ func (e *EthModuleDummy) EthUnsubscribe(ctx context.Context, id ethtypes.EthSubs
|
|||||||
return false, ErrModuleDisabled
|
return false, ErrModuleDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EthModuleDummy) EthTraceBlock(ctx context.Context, blkNum string) ([]*ethtypes.EthTraceBlock, error) {
|
||||||
|
return nil, ErrModuleDisabled
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EthModuleDummy) EthTraceReplayBlockTransactions(ctx context.Context, blkNum string, traceTypes []string) ([]*ethtypes.EthTraceReplayBlockTransaction, error) {
|
||||||
|
return nil, ErrModuleDisabled
|
||||||
|
}
|
||||||
|
|
||||||
var _ EthModuleAPI = &EthModuleDummy{}
|
var _ EthModuleAPI = &EthModuleDummy{}
|
||||||
var _ EthEventAPI = &EthModuleDummy{}
|
var _ EthEventAPI = &EthModuleDummy{}
|
||||||
|
File diff suppressed because it is too large
Load Diff
382
node/impl/full/eth_event.go
Normal file
382
node/impl/full/eth_event.go
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
package full
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"github.com/zyedidia/generic/queue"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-jsonrpc"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/chain/events/filter"
|
||||||
|
"github.com/filecoin-project/lotus/chain/store"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type filterEventCollector interface {
|
||||||
|
TakeCollectedEvents(context.Context) []*filter.CollectedEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterMessageCollector interface {
|
||||||
|
TakeCollectedMessages(context.Context) []*types.SignedMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
type filterTipSetCollector interface {
|
||||||
|
TakeCollectedTipSets(context.Context) []types.TipSetKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethLogFromEvent(entries []types.EventEntry) (data []byte, topics []ethtypes.EthHash, ok bool) {
|
||||||
|
var (
|
||||||
|
topicsFound [4]bool
|
||||||
|
topicsFoundCount int
|
||||||
|
dataFound bool
|
||||||
|
)
|
||||||
|
// Topics must be non-nil, even if empty. So we might as well pre-allocate for 4 (the max).
|
||||||
|
topics = make([]ethtypes.EthHash, 0, 4)
|
||||||
|
for _, entry := range entries {
|
||||||
|
// Drop events with non-raw topics to avoid mistakes.
|
||||||
|
if entry.Codec != cid.Raw {
|
||||||
|
log.Warnw("did not expect an event entry with a non-raw codec", "codec", entry.Codec, "key", entry.Key)
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
// Check if the key is t1..t4
|
||||||
|
if len(entry.Key) == 2 && "t1" <= entry.Key && entry.Key <= "t4" {
|
||||||
|
// '1' - '1' == 0, etc.
|
||||||
|
idx := int(entry.Key[1] - '1')
|
||||||
|
|
||||||
|
// Drop events with mis-sized topics.
|
||||||
|
if len(entry.Value) != 32 {
|
||||||
|
log.Warnw("got an EVM event topic with an invalid size", "key", entry.Key, "size", len(entry.Value))
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop events with duplicate topics.
|
||||||
|
if topicsFound[idx] {
|
||||||
|
log.Warnw("got a duplicate EVM event topic", "key", entry.Key)
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
topicsFound[idx] = true
|
||||||
|
topicsFoundCount++
|
||||||
|
|
||||||
|
// Extend the topics array
|
||||||
|
for len(topics) <= idx {
|
||||||
|
topics = append(topics, ethtypes.EthHash{})
|
||||||
|
}
|
||||||
|
copy(topics[idx][:], entry.Value)
|
||||||
|
} else if entry.Key == "d" {
|
||||||
|
// Drop events with duplicate data fields.
|
||||||
|
if dataFound {
|
||||||
|
log.Warnw("got duplicate EVM event data")
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
dataFound = true
|
||||||
|
data = entry.Value
|
||||||
|
} else {
|
||||||
|
// Skip entries we don't understand (makes it easier to extend things).
|
||||||
|
// But we warn for now because we don't expect them.
|
||||||
|
log.Warnw("unexpected event entry", "key", entry.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop events with skipped topics.
|
||||||
|
if len(topics) != topicsFoundCount {
|
||||||
|
log.Warnw("EVM event topic length mismatch", "expected", len(topics), "actual", topicsFoundCount)
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
return data, topics, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethFilterResultFromEvents(evs []*filter.CollectedEvent, sa StateAPI) (*ethtypes.EthFilterResult, error) {
|
||||||
|
res := ðtypes.EthFilterResult{}
|
||||||
|
for _, ev := range evs {
|
||||||
|
log := ethtypes.EthLog{
|
||||||
|
Removed: ev.Reverted,
|
||||||
|
LogIndex: ethtypes.EthUint64(ev.EventIdx),
|
||||||
|
TransactionIndex: ethtypes.EthUint64(ev.MsgIdx),
|
||||||
|
BlockNumber: ethtypes.EthUint64(ev.Height),
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Data, log.Topics, ok = ethLogFromEvent(ev.Entries)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Address, err = ethtypes.EthAddressFromFilecoinAddress(ev.EmitterAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.TransactionHash, err = ethTxHashFromMessageCid(context.TODO(), ev.MsgCid, sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c, err := ev.TipSetKey.Cid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
log.BlockHash, err = ethtypes.EthHashFromCid(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Results = append(res.Results, log)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethFilterResultFromTipSets(tsks []types.TipSetKey) (*ethtypes.EthFilterResult, error) {
|
||||||
|
res := ðtypes.EthFilterResult{}
|
||||||
|
|
||||||
|
for _, tsk := range tsks {
|
||||||
|
c, err := tsk.Cid()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hash, err := ethtypes.EthHashFromCid(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Results = append(res.Results, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethFilterResultFromMessages(cs []*types.SignedMessage, sa StateAPI) (*ethtypes.EthFilterResult, error) {
|
||||||
|
res := ðtypes.EthFilterResult{}
|
||||||
|
|
||||||
|
for _, c := range cs {
|
||||||
|
hash, err := ethTxHashFromSignedMessage(context.TODO(), c, sa)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Results = append(res.Results, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type EthSubscriptionManager struct {
|
||||||
|
Chain *store.ChainStore
|
||||||
|
StateAPI StateAPI
|
||||||
|
ChainAPI ChainAPI
|
||||||
|
mu sync.Mutex
|
||||||
|
subs map[ethtypes.EthSubscriptionID]*ethSubscription
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EthSubscriptionManager) StartSubscription(ctx context.Context, out ethSubscriptionCallback, dropFilter func(context.Context, filter.Filter) error) (*ethSubscription, error) { // nolint
|
||||||
|
rawid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("new uuid: %w", err)
|
||||||
|
}
|
||||||
|
id := ethtypes.EthSubscriptionID{}
|
||||||
|
copy(id[:], rawid[:]) // uuid is 16 bytes
|
||||||
|
|
||||||
|
ctx, quit := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
sub := ðSubscription{
|
||||||
|
Chain: e.Chain,
|
||||||
|
StateAPI: e.StateAPI,
|
||||||
|
ChainAPI: e.ChainAPI,
|
||||||
|
uninstallFilter: dropFilter,
|
||||||
|
id: id,
|
||||||
|
in: make(chan interface{}, 200),
|
||||||
|
out: out,
|
||||||
|
quit: quit,
|
||||||
|
|
||||||
|
toSend: queue.New[[]byte](),
|
||||||
|
sendCond: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
e.mu.Lock()
|
||||||
|
if e.subs == nil {
|
||||||
|
e.subs = make(map[ethtypes.EthSubscriptionID]*ethSubscription)
|
||||||
|
}
|
||||||
|
e.subs[sub.id] = sub
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
go sub.start(ctx)
|
||||||
|
go sub.startOut(ctx)
|
||||||
|
|
||||||
|
return sub, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *EthSubscriptionManager) StopSubscription(ctx context.Context, id ethtypes.EthSubscriptionID) error {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
sub, ok := e.subs[id]
|
||||||
|
if !ok {
|
||||||
|
return xerrors.Errorf("subscription not found")
|
||||||
|
}
|
||||||
|
sub.stop()
|
||||||
|
delete(e.subs, id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ethSubscriptionCallback func(context.Context, jsonrpc.RawParams) error
|
||||||
|
|
||||||
|
const maxSendQueue = 20000
|
||||||
|
|
||||||
|
type ethSubscription struct {
|
||||||
|
Chain *store.ChainStore
|
||||||
|
StateAPI StateAPI
|
||||||
|
ChainAPI ChainAPI
|
||||||
|
uninstallFilter func(context.Context, filter.Filter) error
|
||||||
|
id ethtypes.EthSubscriptionID
|
||||||
|
in chan interface{}
|
||||||
|
out ethSubscriptionCallback
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
filters []filter.Filter
|
||||||
|
quit func()
|
||||||
|
|
||||||
|
sendLk sync.Mutex
|
||||||
|
sendQueueLen int
|
||||||
|
toSend *queue.Queue[[]byte]
|
||||||
|
sendCond chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethSubscription) addFilter(ctx context.Context, f filter.Filter) {
|
||||||
|
e.mu.Lock()
|
||||||
|
defer e.mu.Unlock()
|
||||||
|
|
||||||
|
f.SetSubChannel(e.in)
|
||||||
|
e.filters = append(e.filters, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendOut processes the final subscription queue. It's here in case the subscriber
|
||||||
|
// is slow, and we need to buffer the messages.
|
||||||
|
func (e *ethSubscription) startOut(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-e.sendCond:
|
||||||
|
e.sendLk.Lock()
|
||||||
|
|
||||||
|
for !e.toSend.Empty() {
|
||||||
|
front := e.toSend.Dequeue()
|
||||||
|
e.sendQueueLen--
|
||||||
|
|
||||||
|
e.sendLk.Unlock()
|
||||||
|
|
||||||
|
if err := e.out(ctx, front); err != nil {
|
||||||
|
log.Warnw("error sending subscription response, killing subscription", "sub", e.id, "error", err)
|
||||||
|
e.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.sendLk.Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
e.sendLk.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethSubscription) send(ctx context.Context, v interface{}) {
|
||||||
|
resp := ethtypes.EthSubscriptionResponse{
|
||||||
|
SubscriptionID: e.id,
|
||||||
|
Result: v,
|
||||||
|
}
|
||||||
|
|
||||||
|
outParam, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnw("marshaling subscription response", "sub", e.id, "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
e.sendLk.Lock()
|
||||||
|
defer e.sendLk.Unlock()
|
||||||
|
|
||||||
|
e.toSend.Enqueue(outParam)
|
||||||
|
|
||||||
|
e.sendQueueLen++
|
||||||
|
if e.sendQueueLen > maxSendQueue {
|
||||||
|
log.Warnw("subscription send queue full, killing subscription", "sub", e.id)
|
||||||
|
e.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case e.sendCond <- struct{}{}:
|
||||||
|
default: // already signalled, and we're holding the lock so we know that the event will be processed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethSubscription) start(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case v := <-e.in:
|
||||||
|
switch vt := v.(type) {
|
||||||
|
case *filter.CollectedEvent:
|
||||||
|
evs, err := ethFilterResultFromEvents([]*filter.CollectedEvent{vt}, e.StateAPI)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range evs.Results {
|
||||||
|
e.send(ctx, r)
|
||||||
|
}
|
||||||
|
case *types.TipSet:
|
||||||
|
ev, err := newEthBlockFromFilecoinTipSet(ctx, vt, true, e.Chain, e.StateAPI)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
e.send(ctx, ev)
|
||||||
|
case *types.SignedMessage: // mpool txid
|
||||||
|
evs, err := ethFilterResultFromMessages([]*types.SignedMessage{vt}, e.StateAPI)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range evs.Results {
|
||||||
|
e.send(ctx, r)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Warnf("unexpected subscription value type: %T", vt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ethSubscription) stop() {
|
||||||
|
e.mu.Lock()
|
||||||
|
if e.quit == nil {
|
||||||
|
e.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.quit != nil {
|
||||||
|
e.quit()
|
||||||
|
e.quit = nil
|
||||||
|
e.mu.Unlock()
|
||||||
|
|
||||||
|
for _, f := range e.filters {
|
||||||
|
// note: the context in actually unused in uninstallFilter
|
||||||
|
if err := e.uninstallFilter(context.TODO(), f); err != nil {
|
||||||
|
// this will leave the filter a zombie, collecting events up to the maximum allowed
|
||||||
|
log.Warnf("failed to remove filter when unsubscribing: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
353
node/impl/full/eth_trace.go
Normal file
353
node/impl/full/eth_trace.go
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
package full
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/multiformats/go-multicodec"
|
||||||
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
"github.com/filecoin-project/go-state-types/builtin"
|
||||||
|
"github.com/filecoin-project/go-state-types/builtin/v10/evm"
|
||||||
|
"github.com/filecoin-project/go-state-types/exitcode"
|
||||||
|
|
||||||
|
builtinactors "github.com/filecoin-project/lotus/chain/actors/builtin"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decodePayload is a utility function which decodes the payload using the given codec
|
||||||
|
func decodePayload(payload []byte, codec uint64) (ethtypes.EthBytes, error) {
|
||||||
|
if len(payload) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch multicodec.Code(codec) {
|
||||||
|
case multicodec.Identity:
|
||||||
|
return nil, nil
|
||||||
|
case multicodec.DagCbor, multicodec.Cbor:
|
||||||
|
buf, err := cbg.ReadByteArray(bytes.NewReader(payload), uint64(len(payload)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("decodePayload: failed to decode cbor payload: %w", err)
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
case multicodec.Raw:
|
||||||
|
return ethtypes.EthBytes(payload), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, xerrors.Errorf("decodePayload: unsupported codec: %d", codec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTraces recursively builds the traces for a given ExecutionTrace by walking the subcalls
|
||||||
|
func buildTraces(ctx context.Context, traces *[]*ethtypes.EthTrace, parent *ethtypes.EthTrace, addr []int, et types.ExecutionTrace, height int64, sa StateAPI) error {
|
||||||
|
// lookup the eth address from the from/to addresses. Note that this may fail but to support
|
||||||
|
// this we need to include the ActorID in the trace. For now, just log a warning and skip
|
||||||
|
// this trace.
|
||||||
|
//
|
||||||
|
// TODO: Add ActorID in trace, see https://github.com/filecoin-project/lotus/pull/11100#discussion_r1302442288
|
||||||
|
from, err := lookupEthAddress(ctx, et.Msg.From, sa)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("buildTraces: failed to lookup from address %s: %v", et.Msg.From, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
to, err := lookupEthAddress(ctx, et.Msg.To, sa)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("buildTraces: failed to lookup to address %s: %w", et.Msg.To, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
trace := ðtypes.EthTrace{
|
||||||
|
Action: ethtypes.EthTraceAction{
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
Gas: ethtypes.EthUint64(et.Msg.GasLimit),
|
||||||
|
Input: nil,
|
||||||
|
Value: ethtypes.EthBigInt(et.Msg.Value),
|
||||||
|
|
||||||
|
FilecoinFrom: et.Msg.From,
|
||||||
|
FilecoinTo: et.Msg.To,
|
||||||
|
FilecoinMethod: et.Msg.Method,
|
||||||
|
FilecoinCodeCid: et.Msg.CodeCid,
|
||||||
|
},
|
||||||
|
Result: ethtypes.EthTraceResult{
|
||||||
|
GasUsed: ethtypes.EthUint64(et.SumGas().TotalGas),
|
||||||
|
Output: nil,
|
||||||
|
},
|
||||||
|
Subtraces: 0, // will be updated by the children once they are added to the trace
|
||||||
|
TraceAddress: addr,
|
||||||
|
|
||||||
|
Parent: parent,
|
||||||
|
LastByteCode: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
trace.SetCallType("call")
|
||||||
|
|
||||||
|
if et.Msg.Method == builtin.MethodsEVM.InvokeContract {
|
||||||
|
log.Debugf("COND1 found InvokeContract call at height: %d", height)
|
||||||
|
|
||||||
|
// TODO: ignore return errors since actors can send gibberish and we don't want
|
||||||
|
// to fail the whole trace in that case
|
||||||
|
trace.Action.Input, err = decodePayload(et.Msg.Params, et.Msg.ParamsCodec)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
trace.Result.Output, err = decodePayload(et.MsgRct.Return, et.MsgRct.ReturnCodec)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
} else if et.Msg.To == builtin.EthereumAddressManagerActorAddr &&
|
||||||
|
et.Msg.Method == builtin.MethodsEAM.CreateExternal {
|
||||||
|
log.Debugf("COND2 found CreateExternal call at height: %d", height)
|
||||||
|
trace.Action.Input, err = decodePayload(et.Msg.Params, et.Msg.ParamsCodec)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if et.MsgRct.ExitCode.IsSuccess() {
|
||||||
|
// ignore return value
|
||||||
|
trace.Result.Output = nil
|
||||||
|
} else {
|
||||||
|
// return value is the error message
|
||||||
|
trace.Result.Output, err = decodePayload(et.MsgRct.Return, et.MsgRct.ReturnCodec)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// treat this as a contract creation
|
||||||
|
trace.SetCallType("create")
|
||||||
|
} else {
|
||||||
|
// we are going to assume a native method, but we may change it in one of the edge cases below
|
||||||
|
// TODO: only do this if we know it's a native method (optimization)
|
||||||
|
trace.Action.Input, err = handleFilecoinMethodInput(et.Msg.Method, et.Msg.ParamsCodec, et.Msg.Params)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
trace.Result.Output, err = handleFilecoinMethodOutput(et.MsgRct.ExitCode, et.MsgRct.ReturnCodec, et.MsgRct.Return)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("buildTraces: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: is it OK to check this here or is this only specific to certain edge case (evm to evm)?
|
||||||
|
if et.Msg.ReadOnly {
|
||||||
|
trace.SetCallType("staticcall")
|
||||||
|
}
|
||||||
|
|
||||||
|
// there are several edge cases that require special handling when displaying the traces. Note that while iterating over
|
||||||
|
// the traces we update the trace backwards (through the parent pointer)
|
||||||
|
if parent != nil {
|
||||||
|
// Handle Native actor creation
|
||||||
|
//
|
||||||
|
// Actor A calls to the init actor on method 2 and The init actor creates the target actor B then calls it on method 1
|
||||||
|
if parent.Action.FilecoinTo == builtin.InitActorAddr &&
|
||||||
|
parent.Action.FilecoinMethod == builtin.MethodsInit.Exec &&
|
||||||
|
et.Msg.Method == builtin.MethodConstructor {
|
||||||
|
log.Debugf("COND3 Native actor creation! method:%d, code:%s, height:%d", et.Msg.Method, et.Msg.CodeCid.String(), height)
|
||||||
|
parent.SetCallType("create")
|
||||||
|
parent.Action.To = to
|
||||||
|
parent.Action.Input = []byte{0xFE}
|
||||||
|
parent.Result.Output = nil
|
||||||
|
|
||||||
|
// there should never be any subcalls when creating a native actor
|
||||||
|
//
|
||||||
|
// TODO: add support for native actors calling another when created
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle EVM contract creation
|
||||||
|
//
|
||||||
|
// To detect EVM contract creation we need to check for the following sequence of events:
|
||||||
|
//
|
||||||
|
// 1) EVM contract A calls the EAM (Ethereum Address Manager) on method 2 (create) or 3 (create2).
|
||||||
|
// 2) The EAM calls the init actor on method 3 (Exec4).
|
||||||
|
// 3) The init actor creates the target actor B then calls it on method 1.
|
||||||
|
if parent.Parent != nil {
|
||||||
|
calledCreateOnEAM := parent.Parent.Action.FilecoinTo == builtin.EthereumAddressManagerActorAddr &&
|
||||||
|
(parent.Parent.Action.FilecoinMethod == builtin.MethodsEAM.Create || parent.Parent.Action.FilecoinMethod == builtin.MethodsEAM.Create2)
|
||||||
|
eamCalledInitOnExec4 := parent.Action.FilecoinTo == builtin.InitActorAddr &&
|
||||||
|
parent.Action.FilecoinMethod == builtin.MethodsInit.Exec4
|
||||||
|
initCreatedActor := trace.Action.FilecoinMethod == builtin.MethodConstructor
|
||||||
|
|
||||||
|
// TODO: We need to handle failures in contract creations and support resurrections on an existing but dead EVM actor)
|
||||||
|
if calledCreateOnEAM && eamCalledInitOnExec4 && initCreatedActor {
|
||||||
|
log.Debugf("COND4 EVM contract creation method:%d, code:%s, height:%d", et.Msg.Method, et.Msg.CodeCid.String(), height)
|
||||||
|
|
||||||
|
if parent.Parent.Action.FilecoinMethod == builtin.MethodsEAM.Create {
|
||||||
|
parent.Parent.SetCallType("create")
|
||||||
|
} else {
|
||||||
|
parent.Parent.SetCallType("create2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the parent.parent to make this
|
||||||
|
parent.Parent.Action.To = trace.Action.To
|
||||||
|
parent.Parent.Subtraces = 0
|
||||||
|
|
||||||
|
// delete the parent (the EAM) and skip the current trace (init)
|
||||||
|
*traces = (*traces)[:len(*traces)-1]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if builtinactors.IsEvmActor(parent.Action.FilecoinCodeCid) {
|
||||||
|
// Handle delegate calls
|
||||||
|
//
|
||||||
|
// 1) Look for trace from an EVM actor to itself on InvokeContractDelegate, method 6.
|
||||||
|
// 2) Check that the previous trace calls another actor on method 3 (GetByteCode) and they are at the same level (same parent)
|
||||||
|
// 3) Treat this as a delegate call to actor A.
|
||||||
|
if parent.LastByteCode != nil && trace.Action.From == trace.Action.To &&
|
||||||
|
trace.Action.FilecoinMethod == builtin.MethodsEVM.InvokeContractDelegate {
|
||||||
|
log.Debugf("COND7 found delegate call, height: %d", height)
|
||||||
|
prev := parent.LastByteCode
|
||||||
|
if prev.Action.From == trace.Action.From && prev.Action.FilecoinMethod == builtin.MethodsEVM.GetBytecode && prev.Parent == trace.Parent {
|
||||||
|
trace.SetCallType("delegatecall")
|
||||||
|
trace.Action.To = prev.Action.To
|
||||||
|
|
||||||
|
var dp evm.DelegateCallParams
|
||||||
|
err := dp.UnmarshalCBOR(bytes.NewReader(et.Msg.Params))
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("failed UnmarshalCBOR: %w", err)
|
||||||
|
}
|
||||||
|
trace.Action.Input = dp.Input
|
||||||
|
|
||||||
|
trace.Result.Output, err = decodePayload(et.MsgRct.Return, et.MsgRct.ReturnCodec)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("failed decodePayload: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle EVM call special casing
|
||||||
|
//
|
||||||
|
// Any outbound call from an EVM actor on methods 1-1023 are side-effects from EVM instructions
|
||||||
|
// and should be dropped from the trace.
|
||||||
|
if et.Msg.Method > 0 &&
|
||||||
|
et.Msg.Method <= 1023 {
|
||||||
|
log.Debugf("Infof found outbound call from an EVM actor on method 1-1023 method:%d, code:%s, height:%d", et.Msg.Method, parent.Action.FilecoinCodeCid.String(), height)
|
||||||
|
|
||||||
|
if et.Msg.Method == builtin.MethodsEVM.GetBytecode {
|
||||||
|
// save the last bytecode trace to handle delegate calls
|
||||||
|
parent.LastByteCode = trace
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are adding trace to the traces so update the parent subtraces count as it was originally set to zero
|
||||||
|
if parent != nil {
|
||||||
|
parent.Subtraces++
|
||||||
|
}
|
||||||
|
|
||||||
|
*traces = append(*traces, trace)
|
||||||
|
|
||||||
|
for i, call := range et.Subcalls {
|
||||||
|
err := buildTraces(ctx, traces, trace, append(addr, i), call, height, sa)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePadded(w io.Writer, data any, size int) error {
|
||||||
|
tmp := &bytes.Buffer{}
|
||||||
|
|
||||||
|
// first write data to tmp buffer to get the size
|
||||||
|
err := binary.Write(tmp, binary.BigEndian, data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writePadded: failed writing tmp data to buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tmp.Len() > size {
|
||||||
|
return fmt.Errorf("writePadded: data is larger than size")
|
||||||
|
}
|
||||||
|
|
||||||
|
// write tailing zeros to pad up to size
|
||||||
|
cnt := size - tmp.Len()
|
||||||
|
for i := 0; i < cnt; i++ {
|
||||||
|
err = binary.Write(w, binary.BigEndian, uint8(0))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writePadded: failed writing tailing zeros to buffer: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally write the actual value
|
||||||
|
err = binary.Write(w, binary.BigEndian, tmp.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("writePadded: failed writing data to buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFilecoinMethodInput(method abi.MethodNum, codec uint64, params []byte) ([]byte, error) {
|
||||||
|
NATIVE_METHOD_SELECTOR := []byte{0x86, 0x8e, 0x10, 0xc4}
|
||||||
|
EVM_WORD_SIZE := 32
|
||||||
|
|
||||||
|
staticArgs := []uint64{
|
||||||
|
uint64(method),
|
||||||
|
codec,
|
||||||
|
uint64(EVM_WORD_SIZE) * 3,
|
||||||
|
uint64(len(params)),
|
||||||
|
}
|
||||||
|
totalWords := len(staticArgs) + (len(params) / EVM_WORD_SIZE)
|
||||||
|
if len(params)%EVM_WORD_SIZE != 0 {
|
||||||
|
totalWords++
|
||||||
|
}
|
||||||
|
len := 4 + totalWords*EVM_WORD_SIZE
|
||||||
|
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
err := binary.Write(w, binary.BigEndian, NATIVE_METHOD_SELECTOR)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodInput: failed writing method selector: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range staticArgs {
|
||||||
|
err := writePadded(w, arg, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodInput: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = binary.Write(w, binary.BigEndian, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodInput: failed writing params: %w", err)
|
||||||
|
}
|
||||||
|
remain := len - w.Len()
|
||||||
|
for i := 0; i < remain; i++ {
|
||||||
|
err = binary.Write(w, binary.BigEndian, uint8(0))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodInput: failed writing tailing zeros: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFilecoinMethodOutput(exitCode exitcode.ExitCode, codec uint64, data []byte) ([]byte, error) {
|
||||||
|
w := &bytes.Buffer{}
|
||||||
|
|
||||||
|
values := []interface{}{uint32(exitCode), codec, uint32(w.Len()), uint32(len(data))}
|
||||||
|
for _, v := range values {
|
||||||
|
err := writePadded(w, v, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodOutput: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := binary.Write(w, binary.BigEndian, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("handleFilecoinMethodOutput: failed writing data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Bytes(), nil
|
||||||
|
}
|
689
node/impl/full/eth_utils.go
Normal file
689
node/impl/full/eth_utils.go
Normal file
@ -0,0 +1,689 @@
|
|||||||
|
package full
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ipfs/go-cid"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-address"
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
|
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||||
|
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
||||||
|
"github.com/filecoin-project/go-state-types/crypto"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/build"
|
||||||
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
|
"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/chain/vm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getTipsetByBlockNumber(ctx context.Context, chain *store.ChainStore, blkParam string, strict bool) (*types.TipSet, error) {
|
||||||
|
if blkParam == "earliest" {
|
||||||
|
return nil, fmt.Errorf("block param \"earliest\" is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
head := chain.GetHeaviestTipSet()
|
||||||
|
switch blkParam {
|
||||||
|
case "pending":
|
||||||
|
return head, nil
|
||||||
|
case "latest":
|
||||||
|
parent, err := chain.GetTipSetFromKey(ctx, head.Parents())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get parent tipset")
|
||||||
|
}
|
||||||
|
return parent, nil
|
||||||
|
default:
|
||||||
|
var num ethtypes.EthUint64
|
||||||
|
err := num.UnmarshalJSON([]byte(`"` + blkParam + `"`))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot parse block number: %v", err)
|
||||||
|
}
|
||||||
|
if abi.ChainEpoch(num) > head.Height()-1 {
|
||||||
|
return nil, fmt.Errorf("requested a future epoch (beyond 'latest')")
|
||||||
|
}
|
||||||
|
ts, err := chain.GetTipsetByHeight(ctx, abi.ChainEpoch(num), head, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get tipset at height: %v", num)
|
||||||
|
}
|
||||||
|
if strict && ts.Height() != abi.ChainEpoch(num) {
|
||||||
|
return nil, ErrNullRound
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTipsetByEthBlockNumberOrHash(ctx context.Context, chain *store.ChainStore, blkParam ethtypes.EthBlockNumberOrHash) (*types.TipSet, error) {
|
||||||
|
head := chain.GetHeaviestTipSet()
|
||||||
|
|
||||||
|
predefined := blkParam.PredefinedBlock
|
||||||
|
if predefined != nil {
|
||||||
|
if *predefined == "earliest" {
|
||||||
|
return nil, fmt.Errorf("block param \"earliest\" is not supported")
|
||||||
|
} else if *predefined == "pending" {
|
||||||
|
return head, nil
|
||||||
|
} else if *predefined == "latest" {
|
||||||
|
parent, err := chain.GetTipSetFromKey(ctx, head.Parents())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get parent tipset")
|
||||||
|
}
|
||||||
|
return parent, nil
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("unknown predefined block %s", *predefined)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if blkParam.BlockNumber != nil {
|
||||||
|
height := abi.ChainEpoch(*blkParam.BlockNumber)
|
||||||
|
if height > head.Height()-1 {
|
||||||
|
return nil, fmt.Errorf("requested a future epoch (beyond 'latest')")
|
||||||
|
}
|
||||||
|
ts, err := chain.GetTipsetByHeight(ctx, height, head, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get tipset at height: %v", height)
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if blkParam.BlockHash != nil {
|
||||||
|
ts, err := chain.GetTipSetByCid(ctx, blkParam.BlockHash.ToCid())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get tipset by hash: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that the tipset is in the canonical chain
|
||||||
|
if blkParam.RequireCanonical {
|
||||||
|
// walk up the current chain (our head) until we reach ts.Height()
|
||||||
|
walkTs, err := chain.GetTipsetByHeight(ctx, ts.Height(), head, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot get tipset at height: %v", ts.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that it equals the expected tipset
|
||||||
|
if !walkTs.Equals(ts) {
|
||||||
|
return nil, fmt.Errorf("tipset is not canonical")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("invalid block param")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) {
|
||||||
|
var from address.Address
|
||||||
|
if tx.From == nil || *tx.From == (ethtypes.EthAddress{}) {
|
||||||
|
// Send from the filecoin "system" address.
|
||||||
|
var err error
|
||||||
|
from, err = (ethtypes.EthAddress{}).ToFilecoinAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to construct the ethereum system address: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The from address must be translatable to an f4 address.
|
||||||
|
var err error
|
||||||
|
from, err = tx.From.ToFilecoinAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to translate sender address (%s): %w", tx.From.String(), err)
|
||||||
|
}
|
||||||
|
if p := from.Protocol(); p != address.Delegated {
|
||||||
|
return nil, fmt.Errorf("expected a class 4 address, got: %d: %w", p, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var params []byte
|
||||||
|
if len(tx.Data) > 0 {
|
||||||
|
initcode := abi.CborBytes(tx.Data)
|
||||||
|
params2, err := actors.SerializeParams(&initcode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to serialize params: %w", err)
|
||||||
|
}
|
||||||
|
params = params2
|
||||||
|
}
|
||||||
|
|
||||||
|
var to address.Address
|
||||||
|
var method abi.MethodNum
|
||||||
|
if tx.To == nil {
|
||||||
|
// this is a contract creation
|
||||||
|
to = builtintypes.EthereumAddressManagerActorAddr
|
||||||
|
method = builtintypes.MethodsEAM.CreateExternal
|
||||||
|
} else {
|
||||||
|
addr, err := tx.To.ToFilecoinAddress()
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("cannot get Filecoin address: %w", err)
|
||||||
|
}
|
||||||
|
to = addr
|
||||||
|
method = builtintypes.MethodsEVM.InvokeContract
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.Message{
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
Value: big.Int(tx.Value),
|
||||||
|
Method: method,
|
||||||
|
Params: params,
|
||||||
|
GasLimit: build.BlockGasLimit,
|
||||||
|
GasFeeCap: big.Zero(),
|
||||||
|
GasPremium: big.Zero(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEthBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool, cs *store.ChainStore, sa StateAPI) (ethtypes.EthBlock, error) {
|
||||||
|
parentKeyCid, err := ts.Parents().Cid()
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, err
|
||||||
|
}
|
||||||
|
parentBlkHash, err := ethtypes.EthHashFromCid(parentKeyCid)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bn := ethtypes.EthUint64(ts.Height())
|
||||||
|
|
||||||
|
blkCid, err := ts.Key().Cid()
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, err
|
||||||
|
}
|
||||||
|
blkHash, err := ethtypes.EthHashFromCid(blkCid)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
msgs, rcpts, err := messagesAndReceipts(ctx, ts, cs, sa)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, xerrors.Errorf("failed to retrieve messages and receipts: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block := ethtypes.NewEthBlock(len(msgs) > 0)
|
||||||
|
|
||||||
|
gasUsed := int64(0)
|
||||||
|
for i, msg := range msgs {
|
||||||
|
rcpt := rcpts[i]
|
||||||
|
ti := ethtypes.EthUint64(i)
|
||||||
|
gasUsed += rcpt.GasUsed
|
||||||
|
var smsg *types.SignedMessage
|
||||||
|
switch msg := msg.(type) {
|
||||||
|
case *types.SignedMessage:
|
||||||
|
smsg = msg
|
||||||
|
case *types.Message:
|
||||||
|
smsg = &types.SignedMessage{
|
||||||
|
Message: *msg,
|
||||||
|
Signature: crypto.Signature{
|
||||||
|
Type: crypto.SigTypeBLS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ethtypes.EthBlock{}, xerrors.Errorf("failed to get signed msg %s: %w", msg.Cid(), err)
|
||||||
|
}
|
||||||
|
tx, err := newEthTxFromSignedMessage(ctx, smsg, sa)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthBlock{}, xerrors.Errorf("failed to convert msg to ethTx: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId)
|
||||||
|
tx.BlockHash = &blkHash
|
||||||
|
tx.BlockNumber = &bn
|
||||||
|
tx.TransactionIndex = &ti
|
||||||
|
|
||||||
|
if fullTxInfo {
|
||||||
|
block.Transactions = append(block.Transactions, tx)
|
||||||
|
} else {
|
||||||
|
block.Transactions = append(block.Transactions, tx.Hash.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
block.Hash = blkHash
|
||||||
|
block.Number = bn
|
||||||
|
block.ParentHash = parentBlkHash
|
||||||
|
block.Timestamp = ethtypes.EthUint64(ts.Blocks()[0].Timestamp)
|
||||||
|
block.BaseFeePerGas = ethtypes.EthBigInt{Int: ts.Blocks()[0].ParentBaseFee.Int}
|
||||||
|
block.GasUsed = ethtypes.EthUint64(gasUsed)
|
||||||
|
return block, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func messagesAndReceipts(ctx context.Context, ts *types.TipSet, cs *store.ChainStore, sa StateAPI) ([]types.ChainMsg, []types.MessageReceipt, error) {
|
||||||
|
msgs, err := cs.MessagesForTipset(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, rcptRoot, err := sa.StateManager.TipSetState(ctx, ts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, xerrors.Errorf("failed to compute state: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rcpts, err := cs.ReadReceipts(ctx, rcptRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, xerrors.Errorf("error loading receipts for tipset: %v: %w", ts, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(msgs) != len(rcpts) {
|
||||||
|
return nil, nil, xerrors.Errorf("receipts and message array lengths didn't match for tipset: %v: %w", ts, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return msgs, rcpts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorFunctionSelector = "\x08\xc3\x79\xa0" // Error(string)
|
||||||
|
const panicFunctionSelector = "\x4e\x48\x7b\x71" // Panic(uint256)
|
||||||
|
// Eth ABI (solidity) panic codes.
|
||||||
|
var panicErrorCodes map[uint64]string = map[uint64]string{
|
||||||
|
0x00: "Panic()",
|
||||||
|
0x01: "Assert()",
|
||||||
|
0x11: "ArithmeticOverflow()",
|
||||||
|
0x12: "DivideByZero()",
|
||||||
|
0x21: "InvalidEnumVariant()",
|
||||||
|
0x22: "InvalidStorageArray()",
|
||||||
|
0x31: "PopEmptyArray()",
|
||||||
|
0x32: "ArrayIndexOutOfBounds()",
|
||||||
|
0x41: "OutOfMemory()",
|
||||||
|
0x51: "CalledUninitializedFunction()",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse an ABI encoded revert reason. This reason should be encoded as if it were the parameters to
|
||||||
|
// an `Error(string)` function call.
|
||||||
|
//
|
||||||
|
// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
|
||||||
|
func parseEthRevert(ret []byte) string {
|
||||||
|
if len(ret) == 0 {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
var cbytes abi.CborBytes
|
||||||
|
if err := cbytes.UnmarshalCBOR(bytes.NewReader(ret)); err != nil {
|
||||||
|
return "ERROR: revert reason is not cbor encoded bytes"
|
||||||
|
}
|
||||||
|
if len(cbytes) == 0 {
|
||||||
|
return "none"
|
||||||
|
}
|
||||||
|
// If it's not long enough to contain an ABI encoded response, return immediately.
|
||||||
|
if len(cbytes) < 4+32 {
|
||||||
|
return ethtypes.EthBytes(cbytes).String()
|
||||||
|
}
|
||||||
|
switch string(cbytes[:4]) {
|
||||||
|
case panicFunctionSelector:
|
||||||
|
cbytes := cbytes[4 : 4+32]
|
||||||
|
// Read the and check the code.
|
||||||
|
code, err := ethtypes.EthUint64FromBytes(cbytes)
|
||||||
|
if err != nil {
|
||||||
|
// If it's too big, just return the raw value.
|
||||||
|
codeInt := big.PositiveFromUnsignedBytes(cbytes)
|
||||||
|
return fmt.Sprintf("Panic(%s)", ethtypes.EthBigInt(codeInt).String())
|
||||||
|
}
|
||||||
|
if s, ok := panicErrorCodes[uint64(code)]; ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Panic(0x%x)", code)
|
||||||
|
case errorFunctionSelector:
|
||||||
|
cbytes := cbytes[4:]
|
||||||
|
cbytesLen := ethtypes.EthUint64(len(cbytes))
|
||||||
|
// Read the and check the offset.
|
||||||
|
offset, err := ethtypes.EthUint64FromBytes(cbytes[:32])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if cbytesLen < offset {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and check the length.
|
||||||
|
if cbytesLen-offset < 32 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start := offset + 32
|
||||||
|
length, err := ethtypes.EthUint64FromBytes(cbytes[offset : offset+32])
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if cbytesLen-start < length {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Slice the error message.
|
||||||
|
return fmt.Sprintf("Error(%s)", cbytes[start:start+length])
|
||||||
|
}
|
||||||
|
return ethtypes.EthBytes(cbytes).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookupEthAddress makes its best effort at finding the Ethereum address for a
|
||||||
|
// Filecoin address. It does the following:
|
||||||
|
//
|
||||||
|
// 1. If the supplied address is an f410 address, we return its payload as the EthAddress.
|
||||||
|
// 2. Otherwise (f0, f1, f2, f3), we look up the actor on the state tree. If it has a delegated address, we return it if it's f410 address.
|
||||||
|
// 3. Otherwise, we fall back to returning a masked ID Ethereum address. If the supplied address is an f0 address, we
|
||||||
|
// use that ID to form the masked ID address.
|
||||||
|
// 4. Otherwise, we fetch the actor's ID from the state tree and form the masked ID with it.
|
||||||
|
func lookupEthAddress(ctx context.Context, addr address.Address, sa StateAPI) (ethtypes.EthAddress, error) {
|
||||||
|
// BLOCK A: We are trying to get an actual Ethereum address from an f410 address.
|
||||||
|
// Attempt to convert directly, if it's an f4 address.
|
||||||
|
ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(addr)
|
||||||
|
if err == nil && !ethAddr.IsMaskedID() {
|
||||||
|
return ethAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup on the target actor and try to get an f410 address.
|
||||||
|
if actor, err := sa.StateGetActor(ctx, addr, types.EmptyTSK); err != nil {
|
||||||
|
return ethtypes.EthAddress{}, err
|
||||||
|
} else if actor.Address != nil {
|
||||||
|
if ethAddr, err := ethtypes.EthAddressFromFilecoinAddress(*actor.Address); err == nil && !ethAddr.IsMaskedID() {
|
||||||
|
return ethAddr, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLOCK B: We gave up on getting an actual Ethereum address and are falling back to a Masked ID address.
|
||||||
|
// Check if we already have an ID addr, and use it if possible.
|
||||||
|
if err == nil && ethAddr.IsMaskedID() {
|
||||||
|
return ethAddr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, resolve the ID addr.
|
||||||
|
idAddr, err := sa.StateLookupID(ctx, addr, types.EmptyTSK)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthAddress{}, err
|
||||||
|
}
|
||||||
|
return ethtypes.EthAddressFromFilecoinAddress(idAddr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseEthTopics(topics ethtypes.EthTopicSpec) (map[string][][]byte, error) {
|
||||||
|
keys := map[string][][]byte{}
|
||||||
|
for idx, vals := range topics {
|
||||||
|
if len(vals) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Ethereum topics are emitted using `LOG{0..4}` opcodes resulting in topics1..4
|
||||||
|
key := fmt.Sprintf("t%d", idx+1)
|
||||||
|
for _, v := range vals {
|
||||||
|
v := v // copy the ethhash to avoid repeatedly referencing the same one.
|
||||||
|
keys[key] = append(keys[key], v[:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethTxHashFromMessageCid(ctx context.Context, c cid.Cid, sa StateAPI) (ethtypes.EthHash, error) {
|
||||||
|
smsg, err := sa.Chain.GetSignedMessage(ctx, c)
|
||||||
|
if err == nil {
|
||||||
|
// This is an Eth Tx, Secp message, Or BLS message in the mpool
|
||||||
|
return ethTxHashFromSignedMessage(ctx, smsg, sa)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = sa.Chain.GetMessage(ctx, c)
|
||||||
|
if err == nil {
|
||||||
|
// This is a BLS message
|
||||||
|
return ethtypes.EthHashFromCid(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ethtypes.EmptyEthHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ethTxHashFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthHash, error) {
|
||||||
|
if smsg.Signature.Type == crypto.SigTypeDelegated {
|
||||||
|
ethTx, err := newEthTxFromSignedMessage(ctx, smsg, sa)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EmptyEthHash, err
|
||||||
|
}
|
||||||
|
return ethTx.Hash, nil
|
||||||
|
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 {
|
||||||
|
return ethtypes.EthHashFromCid(smsg.Cid())
|
||||||
|
} else { // BLS message
|
||||||
|
return ethtypes.EthHashFromCid(smsg.Message.Cid())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEthTxFromSignedMessage(ctx context.Context, smsg *types.SignedMessage, sa StateAPI) (ethtypes.EthTx, error) {
|
||||||
|
var tx ethtypes.EthTx
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// This is an eth tx
|
||||||
|
if smsg.Signature.Type == crypto.SigTypeDelegated {
|
||||||
|
tx, err = ethtypes.EthTxFromSignedEthMessage(smsg)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, xerrors.Errorf("failed to convert from signed message: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Hash, err = tx.TxHash()
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, xerrors.Errorf("failed to calculate hash for ethTx: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fromAddr, err := lookupEthAddress(ctx, smsg.Message.From, sa)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, xerrors.Errorf("failed to resolve Ethereum address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.From = fromAddr
|
||||||
|
} else if smsg.Signature.Type == crypto.SigTypeSecp256k1 { // Secp Filecoin Message
|
||||||
|
tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), sa)
|
||||||
|
tx.Hash, err = ethtypes.EthHashFromCid(smsg.Cid())
|
||||||
|
if err != nil {
|
||||||
|
return tx, err
|
||||||
|
}
|
||||||
|
} else { // BLS Filecoin message
|
||||||
|
tx = ethTxFromNativeMessage(ctx, smsg.VMMessage(), sa)
|
||||||
|
tx.Hash, err = ethtypes.EthHashFromCid(smsg.Message.Cid())
|
||||||
|
if err != nil {
|
||||||
|
return tx, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ethTxFromNativeMessage does NOT populate:
|
||||||
|
// - BlockHash
|
||||||
|
// - BlockNumber
|
||||||
|
// - TransactionIndex
|
||||||
|
// - Hash
|
||||||
|
func ethTxFromNativeMessage(ctx context.Context, msg *types.Message, sa StateAPI) ethtypes.EthTx {
|
||||||
|
// We don't care if we error here, conversion is best effort for non-eth transactions
|
||||||
|
from, _ := lookupEthAddress(ctx, msg.From, sa)
|
||||||
|
to, _ := lookupEthAddress(ctx, msg.To, sa)
|
||||||
|
return ethtypes.EthTx{
|
||||||
|
To: &to,
|
||||||
|
From: from,
|
||||||
|
Nonce: ethtypes.EthUint64(msg.Nonce),
|
||||||
|
ChainID: ethtypes.EthUint64(build.Eip155ChainId),
|
||||||
|
Value: ethtypes.EthBigInt(msg.Value),
|
||||||
|
Type: ethtypes.Eip1559TxType,
|
||||||
|
Gas: ethtypes.EthUint64(msg.GasLimit),
|
||||||
|
MaxFeePerGas: ethtypes.EthBigInt(msg.GasFeeCap),
|
||||||
|
MaxPriorityFeePerGas: ethtypes.EthBigInt(msg.GasPremium),
|
||||||
|
AccessList: []ethtypes.EthHash{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSignedMessage(ctx context.Context, cs *store.ChainStore, msgCid cid.Cid) (*types.SignedMessage, error) {
|
||||||
|
smsg, err := cs.GetSignedMessage(ctx, msgCid)
|
||||||
|
if err != nil {
|
||||||
|
// We couldn't find the signed message, it might be a BLS message, so search for a regular message.
|
||||||
|
msg, err := cs.GetMessage(ctx, msgCid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, xerrors.Errorf("failed to find msg %s: %w", msgCid, err)
|
||||||
|
}
|
||||||
|
smsg = &types.SignedMessage{
|
||||||
|
Message: *msg,
|
||||||
|
Signature: crypto.Signature{
|
||||||
|
Type: crypto.SigTypeBLS,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return smsg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// newEthTxFromMessageLookup creates an ethereum transaction from filecoin message lookup. If a negative txIdx is passed
|
||||||
|
// into the function, it looks up the transaction index of the message in the tipset, otherwise it uses the txIdx passed into the
|
||||||
|
// function
|
||||||
|
func newEthTxFromMessageLookup(ctx context.Context, msgLookup *api.MsgLookup, txIdx int, cs *store.ChainStore, sa StateAPI) (ethtypes.EthTx, error) {
|
||||||
|
ts, err := cs.LoadTipSet(ctx, msgLookup.TipSet)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tx is located in the parent tipset
|
||||||
|
parentTs, err := cs.LoadTipSet(ctx, ts.Parents())
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parentTsCid, err := parentTs.Key().Cid()
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// lookup the transactionIndex
|
||||||
|
if txIdx < 0 {
|
||||||
|
msgs, err := cs.MessagesForTipset(ctx, parentTs)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
for i, msg := range msgs {
|
||||||
|
if msg.Cid() == msgLookup.Message {
|
||||||
|
txIdx = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if txIdx < 0 {
|
||||||
|
return ethtypes.EthTx{}, fmt.Errorf("cannot find the msg in the tipset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blkHash, err := ethtypes.EthHashFromCid(parentTsCid)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
smsg, err := getSignedMessage(ctx, cs, msgLookup.Message)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, xerrors.Errorf("failed to get signed msg: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, err := newEthTxFromSignedMessage(ctx, smsg, sa)
|
||||||
|
if err != nil {
|
||||||
|
return ethtypes.EthTx{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
bn = ethtypes.EthUint64(parentTs.Height())
|
||||||
|
ti = ethtypes.EthUint64(txIdx)
|
||||||
|
)
|
||||||
|
|
||||||
|
tx.ChainID = ethtypes.EthUint64(build.Eip155ChainId)
|
||||||
|
tx.BlockHash = &blkHash
|
||||||
|
tx.BlockNumber = &bn
|
||||||
|
tx.TransactionIndex = &ti
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newEthTxReceipt(ctx context.Context, tx ethtypes.EthTx, lookup *api.MsgLookup, events []types.Event, cs *store.ChainStore, sa StateAPI) (api.EthTxReceipt, error) {
|
||||||
|
var (
|
||||||
|
transactionIndex ethtypes.EthUint64
|
||||||
|
blockHash ethtypes.EthHash
|
||||||
|
blockNumber ethtypes.EthUint64
|
||||||
|
)
|
||||||
|
|
||||||
|
if tx.TransactionIndex != nil {
|
||||||
|
transactionIndex = *tx.TransactionIndex
|
||||||
|
}
|
||||||
|
if tx.BlockHash != nil {
|
||||||
|
blockHash = *tx.BlockHash
|
||||||
|
}
|
||||||
|
if tx.BlockNumber != nil {
|
||||||
|
blockNumber = *tx.BlockNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt := api.EthTxReceipt{
|
||||||
|
TransactionHash: tx.Hash,
|
||||||
|
From: tx.From,
|
||||||
|
To: tx.To,
|
||||||
|
TransactionIndex: transactionIndex,
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: blockNumber,
|
||||||
|
Type: ethtypes.EthUint64(2),
|
||||||
|
Logs: []ethtypes.EthLog{}, // empty log array is compulsory when no logs, or libraries like ethers.js break
|
||||||
|
LogsBloom: ethtypes.EmptyEthBloom[:],
|
||||||
|
}
|
||||||
|
|
||||||
|
if lookup.Receipt.ExitCode.IsSuccess() {
|
||||||
|
receipt.Status = 1
|
||||||
|
} else {
|
||||||
|
receipt.Status = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
receipt.GasUsed = ethtypes.EthUint64(lookup.Receipt.GasUsed)
|
||||||
|
|
||||||
|
// TODO: handle CumulativeGasUsed
|
||||||
|
receipt.CumulativeGasUsed = ethtypes.EmptyEthInt
|
||||||
|
|
||||||
|
// TODO: avoid loading the tipset twice (once here, once when we convert the message to a txn)
|
||||||
|
ts, err := cs.GetTipSetFromKey(ctx, lookup.TipSet)
|
||||||
|
if err != nil {
|
||||||
|
return api.EthTxReceipt{}, xerrors.Errorf("failed to lookup tipset %s when constructing the eth txn receipt: %w", lookup.TipSet, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseFee := ts.Blocks()[0].ParentBaseFee
|
||||||
|
gasOutputs := vm.ComputeGasOutputs(lookup.Receipt.GasUsed, int64(tx.Gas), baseFee, big.Int(tx.MaxFeePerGas), big.Int(tx.MaxPriorityFeePerGas), true)
|
||||||
|
totalSpent := big.Sum(gasOutputs.BaseFeeBurn, gasOutputs.MinerTip, gasOutputs.OverEstimationBurn)
|
||||||
|
|
||||||
|
effectiveGasPrice := big.Zero()
|
||||||
|
if lookup.Receipt.GasUsed > 0 {
|
||||||
|
effectiveGasPrice = big.Div(totalSpent, big.NewInt(lookup.Receipt.GasUsed))
|
||||||
|
}
|
||||||
|
receipt.EffectiveGasPrice = ethtypes.EthBigInt(effectiveGasPrice)
|
||||||
|
|
||||||
|
if receipt.To == nil && lookup.Receipt.ExitCode.IsSuccess() {
|
||||||
|
// Create and Create2 return the same things.
|
||||||
|
var ret eam.CreateExternalReturn
|
||||||
|
if err := ret.UnmarshalCBOR(bytes.NewReader(lookup.Receipt.Return)); err != nil {
|
||||||
|
return api.EthTxReceipt{}, xerrors.Errorf("failed to parse contract creation result: %w", err)
|
||||||
|
}
|
||||||
|
addr := ethtypes.EthAddress(ret.EthAddress)
|
||||||
|
receipt.ContractAddress = &addr
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(events) > 0 {
|
||||||
|
receipt.Logs = make([]ethtypes.EthLog, 0, len(events))
|
||||||
|
for i, evt := range events {
|
||||||
|
l := ethtypes.EthLog{
|
||||||
|
Removed: false,
|
||||||
|
LogIndex: ethtypes.EthUint64(i),
|
||||||
|
TransactionHash: tx.Hash,
|
||||||
|
TransactionIndex: transactionIndex,
|
||||||
|
BlockHash: blockHash,
|
||||||
|
BlockNumber: blockNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
data, topics, ok := ethLogFromEvent(evt.Entries)
|
||||||
|
if !ok {
|
||||||
|
// not an eth event.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, topic := range topics {
|
||||||
|
ethtypes.EthBloomSet(receipt.LogsBloom, topic[:])
|
||||||
|
}
|
||||||
|
l.Data = data
|
||||||
|
l.Topics = topics
|
||||||
|
|
||||||
|
addr, err := address.NewIDAddress(uint64(evt.Emitter))
|
||||||
|
if err != nil {
|
||||||
|
return api.EthTxReceipt{}, xerrors.Errorf("failed to create ID address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Address, err = lookupEthAddress(ctx, addr, sa)
|
||||||
|
if err != nil {
|
||||||
|
return api.EthTxReceipt{}, xerrors.Errorf("failed to resolve Ethereum address: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ethtypes.EthBloomSet(receipt.LogsBloom, l.Address[:])
|
||||||
|
receipt.Logs = append(receipt.Logs, l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return receipt, nil
|
||||||
|
}
|
129
node/impl/full/txhashmanager.go
Normal file
129
node/impl/full/txhashmanager.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package full
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/go-state-types/abi"
|
||||||
|
"github.com/filecoin-project/go-state-types/crypto"
|
||||||
|
|
||||||
|
"github.com/filecoin-project/lotus/api"
|
||||||
|
"github.com/filecoin-project/lotus/build"
|
||||||
|
"github.com/filecoin-project/lotus/chain/ethhashlookup"
|
||||||
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EthTxHashManager struct {
|
||||||
|
StateAPI StateAPI
|
||||||
|
TransactionHashLookup *ethhashlookup.EthTxHashLookup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EthTxHashManager) Revert(ctx context.Context, from, to *types.TipSet) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EthTxHashManager) PopulateExistingMappings(ctx context.Context, minHeight abi.ChainEpoch) error {
|
||||||
|
if minHeight < build.UpgradeHyggeHeight {
|
||||||
|
minHeight = build.UpgradeHyggeHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := m.StateAPI.Chain.GetHeaviestTipSet()
|
||||||
|
for ts.Height() > minHeight {
|
||||||
|
for _, block := range ts.Blocks() {
|
||||||
|
msgs, err := m.StateAPI.Chain.SecpkMessagesForBlock(ctx, block)
|
||||||
|
if err != nil {
|
||||||
|
// If we can't find the messages, we've either imported from snapshot or pruned the store
|
||||||
|
log.Debug("exiting message mapping population at epoch ", ts.Height())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
m.ProcessSignedMessage(ctx, msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
ts, err = m.StateAPI.Chain.GetTipSetFromKey(ctx, ts.Parents())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EthTxHashManager) Apply(ctx context.Context, from, to *types.TipSet) error {
|
||||||
|
for _, blk := range to.Blocks() {
|
||||||
|
_, smsgs, err := m.StateAPI.Chain.MessagesForBlock(ctx, blk)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, smsg := range smsgs {
|
||||||
|
if smsg.Signature.Type != crypto.SigTypeDelegated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hash, err := ethTxHashFromSignedMessage(ctx, smsg, m.StateAPI)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.TransactionHashLookup.UpsertHash(hash, smsg.Cid())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *EthTxHashManager) ProcessSignedMessage(ctx context.Context, msg *types.SignedMessage) {
|
||||||
|
if msg.Signature.Type != crypto.SigTypeDelegated {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ethTx, err := newEthTxFromSignedMessage(ctx, msg, m.StateAPI)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error converting filecoin message to eth tx: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.TransactionHashLookup.UpsertHash(ethTx.Hash, msg.Cid())
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error inserting tx mapping to db: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, manager *EthTxHashManager) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case u := <-ch:
|
||||||
|
if u.Type != api.MpoolAdd {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.ProcessSignedMessage(ctx, u.Message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EthTxHashGC(ctx context.Context, retentionDays int, manager *EthTxHashManager) {
|
||||||
|
if retentionDays == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gcPeriod := 1 * time.Hour
|
||||||
|
for {
|
||||||
|
entriesDeleted, err := manager.TransactionHashLookup.DeleteEntriesOlderThan(retentionDays)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("error garbage collecting eth transaction hash database: %s", err)
|
||||||
|
}
|
||||||
|
log.Info("garbage collection run on eth transaction hash lookup database. %d entries deleted", entriesDeleted)
|
||||||
|
time.Sleep(gcPeriod)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user