From e5bb5b74307495a254dba537075a16ced06e6598 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Mon, 12 Sep 2022 17:46:15 -0400 Subject: [PATCH] feat: ethrpc: implement EthBlock and EthTx structs (#9287) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Raúl Kripalani --- api/api_full.go | 5 +- api/eth_types.go | 102 ++++++++++-- api/eth_types_test.go | 60 +++++-- api/mocks/mock_full.go | 30 ++++ api/proxy_gen.go | 28 +++- build/params_2k.go | 4 + build/params_butterfly.go | 4 + build/params_calibnet.go | 4 + build/params_interop.go | 5 + build/params_mainnet.go | 4 + build/params_testground.go | 4 + build/params_wallaby.go | 4 + chain/types/tipset_key.go | 8 + documentation/en/api-v1-unstable-methods.md | 110 +++++++------ node/impl/full/eth.go | 166 ++++++++++++++++++-- node/rpc.go | 4 +- 16 files changed, 455 insertions(+), 87 deletions(-) diff --git a/api/api_full.go b/api/api_full.go index d9ad75652..f5c5d2d18 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -785,9 +785,10 @@ type FullNode interface { NetVersion(ctx context.Context) (string, error) //perm:read NetListening(ctx context.Context) (bool, error) //perm:read EthProtocolVersion(ctx context.Context) (EthInt, error) //perm:read - EthMaxPriorityFeePerGas(ctx context.Context) (EthInt, error) //perm:read EthGasPrice(ctx context.Context) (EthInt, error) //perm:read - // EthSendRawTransaction(ctx context.Context, tx api.EthTx) (EthHash, error) //perm:write + EthMaxPriorityFeePerGas(ctx context.Context) (EthInt, error) //perm:read + EthEstimateGas(ctx context.Context, tx EthCall, blkParam string) (EthInt, error) //perm:read + EthCall(ctx context.Context, tx EthCall, blkParam string) (string, error) //perm:read // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus daemon is running with the diff --git a/api/eth_types.go b/api/eth_types.go index ba39b161e..bf89c4804 100644 --- a/api/eth_types.go +++ b/api/eth_types.go @@ -5,15 +5,18 @@ import ( "encoding/hex" "encoding/json" "fmt" + mathbig "math/big" "strconv" "strings" "github.com/ipfs/go-cid" "github.com/multiformats/go-multihash" - xerrors "golang.org/x/xerrors" + "golang.org/x/xerrors" "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/big" + + "github.com/filecoin-project/lotus/build" ) type EthInt int64 @@ -32,12 +35,16 @@ func (e *EthInt) UnmarshalJSON(b []byte) error { return err } eint := EthInt(parsedInt) - e = &eint + *e = eint return nil } type EthBigInt big.Int +var ( + EthBigIntZero = EthBigInt{Int: big.Zero().Int} +) + func (e EthBigInt) MarshalJSON() ([]byte, error) { if e.Int == nil { return json.Marshal("0x0") @@ -45,6 +52,24 @@ func (e EthBigInt) MarshalJSON() ([]byte, error) { return json.Marshal(fmt.Sprintf("0x%x", e.Int)) } +func (e *EthBigInt) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + replaced := strings.Replace(s, "0x", "", -1) + if len(replaced)%2 == 1 { + replaced = "0" + replaced + } + + i := new(mathbig.Int) + i.SetString(replaced, 16) + + *e = EthBigInt(big.NewFromGo(i)) + return nil +} + type EthBlock struct { ParentHash EthHash `json:"parentHash"` Sha3Uncles EthHash `json:"sha3Uncles"` @@ -53,24 +78,49 @@ type EthBlock struct { TransactionsRoot EthHash `json:"transactionsRoot"` ReceiptsRoot EthHash `json:"receiptsRoot"` // TODO: include LogsBloom - Difficulty EthInt `json:"difficulty"` - Number EthInt `json:"number"` - GasLimit EthInt `json:"gasLimit"` - GasUsed EthInt `json:"gasUsed"` - Timestamp EthInt `json:"timestamp"` - Extradata []byte `json:"extraData"` - MixHash EthHash `json:"mixHash"` - Nonce EthNonce `json:"nonce"` - BaseFeePerGas EthInt `json:"baseFeePerGas"` - Transactions EthTx `json:"transactions"` + Difficulty EthInt `json:"difficulty"` + Number EthInt `json:"number"` + GasLimit EthInt `json:"gasLimit"` + GasUsed EthInt `json:"gasUsed"` + Timestamp EthInt `json:"timestamp"` + Extradata []byte `json:"extraData"` + MixHash EthHash `json:"mixHash"` + Nonce EthNonce `json:"nonce"` + BaseFeePerGas EthBigInt `json:"baseFeePerGas"` + Size EthInt `json:"size"` + // can be []EthTx or []string depending on query params + Transactions []interface{} `json:"transactions"` + Uncles []EthHash `json:"uncles"` +} + +var ( + EmptyEthHash = EthHash{} + EmptyEthInt = EthInt(0) + EmptyEthNonce = [8]byte{0, 0, 0, 0, 0, 0, 0, 0} +) + +func NewEthBlock() EthBlock { + return EthBlock{ + Sha3Uncles: EmptyEthHash, + StateRoot: EmptyEthHash, + TransactionsRoot: EmptyEthHash, + ReceiptsRoot: EmptyEthHash, + Difficulty: EmptyEthInt, + Extradata: []byte{}, + MixHash: EmptyEthHash, + Nonce: EmptyEthNonce, + GasLimit: EthInt(build.BlockGasLimit), // TODO we map Ethereum blocks to Filecoin tipsets; this is inconsistent. + Uncles: []EthHash{}, + Transactions: []interface{}{}, + } } type EthTx struct { - ChainID *EthInt `json:"chainId"` + ChainID EthInt `json:"chainId"` Nonce uint64 `json:"nonce"` Hash EthHash `json:"hash"` BlockHash EthHash `json:"blockHash"` - BlockNumber EthHash `json:"blockNumber"` + BlockNumber EthInt `json:"blockNumber"` TransactionIndex EthInt `json:"transacionIndex"` From EthAddress `json:"from"` To EthAddress `json:"to"` @@ -85,6 +135,26 @@ type EthTx struct { S EthBigInt `json:"s"` } +type EthCall struct { + From EthAddress `json:"from"` + To EthAddress `json:"to"` + Gas EthInt `json:"gas"` + GasPrice EthBigInt `json:"gasPrice"` + Value EthBigInt `json:"value"` + Data []byte `json:"data"` +} + +func (c *EthCall) UnmarshalJSON(b []byte) error { + type TempEthCall EthCall + var params TempEthCall + + if err := json.Unmarshal(b, ¶ms); err != nil { + return err + } + *c = EthCall(params) + return nil +} + type EthTxReceipt struct { TransactionHash EthHash `json:"transactionHash"` TransactionIndex EthInt `json:"transacionIndex"` @@ -136,7 +206,7 @@ func (a *EthAddress) UnmarshalJSON(b []byte) error { if err != nil { return err } - a = &addr + copy(a[:], addr[:]) return nil } @@ -185,7 +255,7 @@ func (h *EthHash) UnmarshalJSON(b []byte) error { if err != nil { return err } - h = &hash + copy(h[:], hash[:]) return nil } diff --git a/api/eth_types_test.go b/api/eth_types_test.go index a29c0a06a..846931942 100644 --- a/api/eth_types_test.go +++ b/api/eth_types_test.go @@ -30,6 +30,20 @@ func TestEthIntMarshalJSON(t *testing.T) { require.Equal(t, j, tc.Output) } } +func TestEthIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthInt(0)}, + {[]byte("\"0x41\""), EthInt(65)}, + {[]byte("\"0x400\""), EthInt(1024)}, + } + + for _, tc := range testcases { + var i EthInt + err := i.UnmarshalJSON(tc.Input.([]byte)) + require.Nil(t, err) + require.Equal(t, i, tc.Output) + } +} func TestEthBigIntMarshalJSON(t *testing.T) { testcases := []TestCase{ @@ -45,16 +59,34 @@ func TestEthBigIntMarshalJSON(t *testing.T) { } } +func TestEthBigIntUnmarshalJSON(t *testing.T) { + testcases := []TestCase{ + {[]byte("\"0x0\""), EthBigInt(big.MustFromString("0"))}, + {[]byte("\"0x41\""), EthBigInt(big.MustFromString("65"))}, + {[]byte("\"0x400\""), EthBigInt(big.MustFromString("1024"))}, + {[]byte("\"0xff1000000000000000000000000\""), EthBigInt(big.MustFromString("323330131220712761719252861321216"))}, + } + + for _, tc := range testcases { + var i EthBigInt + err := i.UnmarshalJSON(tc.Input.([]byte)) + require.Nil(t, err) + require.Equal(t, i, tc.Output) + } +} + func TestEthHash(t *testing.T) { testcases := []string{ - "0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184", - "0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738", + `"0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184"`, + `"0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738"`, } for _, hash := range testcases { - h, err := EthHashFromHex(hash) + var h EthHash + err := h.UnmarshalJSON([]byte(hash)) + require.Nil(t, err) - require.Equal(t, h.String(), hash) + require.Equal(t, h.String(), strings.Replace(hash, `"`, "", -1)) c := h.ToCid() h1, err := EthHashFromCid(c) @@ -65,15 +97,17 @@ func TestEthHash(t *testing.T) { func TestEthAddr(t *testing.T) { testcases := []string{ - strings.ToLower("0xd4c5fb16488Aa48081296299d54b0c648C9333dA"), - strings.ToLower("0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"), - strings.ToLower("0x01184F793982104363F9a8a5845743f452dE0586"), + strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`), + strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`), + strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`), } for _, addr := range testcases { - a, err := EthAddressFromHex(addr) + var a EthAddress + err := a.UnmarshalJSON([]byte(addr)) + require.Nil(t, err) - require.Equal(t, a.String(), addr) + require.Equal(t, a.String(), strings.Replace(addr, `"`, "", -1)) } } @@ -94,3 +128,11 @@ func TestParseEthAddr(t *testing.T) { require.Equal(t, addr, faddr) } } + +func TestUnmarshalEthCall(t *testing.T) { + data := `{"from":"0x4D6D86b31a112a05A473c4aE84afaF873f632325","to":"0xFe01CC39f5Ae8553D6914DBb9dC27D219fa22D7f","gas":"0x5","gasPrice":"0x6","value":"0x123","data":""}` + + var c EthCall + err := c.UnmarshalJSON([]byte(data)) + require.Nil(t, err) +} diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 5b3918b95..06a5e87ff 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -951,6 +951,21 @@ func (mr *MockFullNodeMockRecorder) EthBlockNumber(arg0 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthBlockNumber", reflect.TypeOf((*MockFullNode)(nil).EthBlockNumber), arg0) } +// EthCall mocks base method. +func (m *MockFullNode) EthCall(arg0 context.Context, arg1 api.EthCall, arg2 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthCall", arg0, arg1, arg2) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthCall indicates an expected call of EthCall. +func (mr *MockFullNodeMockRecorder) EthCall(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthCall", reflect.TypeOf((*MockFullNode)(nil).EthCall), arg0, arg1, arg2) +} + // EthChainId mocks base method. func (m *MockFullNode) EthChainId(arg0 context.Context) (api.EthInt, error) { m.ctrl.T.Helper() @@ -966,6 +981,21 @@ func (mr *MockFullNodeMockRecorder) EthChainId(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthChainId", reflect.TypeOf((*MockFullNode)(nil).EthChainId), arg0) } +// EthEstimateGas mocks base method. +func (m *MockFullNode) EthEstimateGas(arg0 context.Context, arg1 api.EthCall, arg2 string) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthEstimateGas", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthEstimateGas indicates an expected call of EthEstimateGas. +func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthEstimateGas", reflect.TypeOf((*MockFullNode)(nil).EthEstimateGas), arg0, arg1, arg2) +} + // EthGasPrice mocks base method. func (m *MockFullNode) EthGasPrice(arg0 context.Context) (api.EthInt, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index e585a1a5c..8a59b3fb1 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -222,9 +222,13 @@ type FullNodeStruct struct { EthBlockNumber func(p0 context.Context) (EthInt, error) `perm:"read"` + EthCall func(p0 context.Context, p1 EthCall, p2 string) (string, error) `perm:"read"` + EthChainId func(p0 context.Context) (EthInt, error) `perm:"read"` - EthGasPrice func(p0 context.Context) (EthInt, error) `` + EthEstimateGas func(p0 context.Context, p1 EthCall, p2 string) (EthInt, error) `perm:"read"` + + EthGasPrice func(p0 context.Context) (EthInt, error) `perm:"read"` EthGetBalance func(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) `perm:"read"` @@ -1853,6 +1857,17 @@ func (s *FullNodeStub) EthBlockNumber(p0 context.Context) (EthInt, error) { return *new(EthInt), ErrNotSupported } +func (s *FullNodeStruct) EthCall(p0 context.Context, p1 EthCall, p2 string) (string, error) { + if s.Internal.EthCall == nil { + return "", ErrNotSupported + } + return s.Internal.EthCall(p0, p1, p2) +} + +func (s *FullNodeStub) EthCall(p0 context.Context, p1 EthCall, p2 string) (string, error) { + return "", ErrNotSupported +} + func (s *FullNodeStruct) EthChainId(p0 context.Context) (EthInt, error) { if s.Internal.EthChainId == nil { return *new(EthInt), ErrNotSupported @@ -1864,6 +1879,17 @@ func (s *FullNodeStub) EthChainId(p0 context.Context) (EthInt, error) { return *new(EthInt), ErrNotSupported } +func (s *FullNodeStruct) EthEstimateGas(p0 context.Context, p1 EthCall, p2 string) (EthInt, error) { + if s.Internal.EthEstimateGas == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthEstimateGas(p0, p1, p2) +} + +func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 EthCall, p2 string) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (EthInt, error) { if s.Internal.EthGasPrice == nil { return *new(EthInt), ErrNotSupported diff --git a/build/params_2k.go b/build/params_2k.go index 6b8bf04f2..cdcaaa832 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -131,4 +131,8 @@ const InteractivePoRepConfidence = 6 const BootstrapPeerThreshold = 1 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 + var WhitelistedBlock = cid.Undef diff --git a/build/params_butterfly.go b/build/params_butterfly.go index a4ec220ae..ffd5b043b 100644 --- a/build/params_butterfly.go +++ b/build/params_butterfly.go @@ -81,4 +81,8 @@ const PropagationDelaySecs = uint64(6) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 2 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 3141592 + var WhitelistedBlock = cid.Undef diff --git a/build/params_calibnet.go b/build/params_calibnet.go index 2c1fd80b4..52e2d2ecc 100644 --- a/build/params_calibnet.go +++ b/build/params_calibnet.go @@ -114,4 +114,8 @@ var PropagationDelaySecs = uint64(10) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314159 + var WhitelistedBlock = cid.Undef diff --git a/build/params_interop.go b/build/params_interop.go index e111a461d..94da3464a 100644 --- a/build/params_interop.go +++ b/build/params_interop.go @@ -119,4 +119,9 @@ const PropagationDelaySecs = uint64(6) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 2 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +// TODO same as butterfly for now, as we didn't submit an assignment for interopnet. +const Eip155ChainId = 3141592 + var WhitelistedBlock = cid.Undef diff --git a/build/params_mainnet.go b/build/params_mainnet.go index c94162b26..1b0fb148e 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -127,5 +127,9 @@ const BlockDelaySecs = uint64(builtin2.EpochDurationSeconds) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 4 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 314 + // we skip checks on message validity in this block to sidestep the zero-bls signature var WhitelistedBlock = MustParseCid("bafy2bzaceapyg2uyzk7vueh3xccxkuwbz3nxewjyguoxvhx77malc2lzn2ybi") diff --git a/build/params_testground.go b/build/params_testground.go index db8d47919..6cf87bee2 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -130,3 +130,7 @@ var ( ) const BootstrapPeerThreshold = 1 + +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415926 diff --git a/build/params_wallaby.go b/build/params_wallaby.go index 96ce9fe51..6d2c33c7a 100644 --- a/build/params_wallaby.go +++ b/build/params_wallaby.go @@ -85,4 +85,8 @@ const PropagationDelaySecs = uint64(6) // BootstrapPeerThreshold is the minimum number peers we need to track for a sync worker to start const BootstrapPeerThreshold = 2 +// ChainId defines the chain ID used in the Ethereum JSON-RPC endpoint. +// As per https://github.com/ethereum-lists/chains +const Eip155ChainId = 31415 + var WhitelistedBlock = cid.Undef diff --git a/chain/types/tipset_key.go b/chain/types/tipset_key.go index 9dd736e73..15e655da7 100644 --- a/chain/types/tipset_key.go +++ b/chain/types/tipset_key.go @@ -99,6 +99,14 @@ func (k *TipSetKey) UnmarshalJSON(b []byte) error { return nil } +func (k TipSetKey) Cid() (cid.Cid, error) { + blk, err := k.ToStorageBlock() + if err != nil { + return cid.Cid{}, err + } + return blk.Cid(), nil +} + func (k TipSetKey) ToStorageBlock() (block.Block, error) { buf := new(bytes.Buffer) if err := k.MarshalCBOR(buf); err != nil { diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index c0f52db79..0b9235ce4 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -68,7 +68,9 @@ * [Eth](#Eth) * [EthAccounts](#EthAccounts) * [EthBlockNumber](#EthBlockNumber) + * [EthCall](#EthCall) * [EthChainId](#EthChainId) + * [EthEstimateGas](#EthEstimateGas) * [EthGasPrice](#EthGasPrice) * [EthGetBalance](#EthGetBalance) * [EthGetBlockByHash](#EthGetBlockByHash) @@ -2173,6 +2175,28 @@ Inputs: `null` Response: `"0x5"` +### EthCall + + +Perms: read + +Inputs: +```json +[ + { + "from": "0x0707070707070707070707070707070707070707", + "to": "0x0707070707070707070707070707070707070707", + "gas": "0x5", + "gasPrice": "0x0", + "value": "0x0", + "data": "Ynl0ZSBhcnJheQ==" + }, + "string value" +] +``` + +Response: `"string value"` + ### EthChainId @@ -2182,10 +2206,32 @@ Inputs: `null` Response: `"0x5"` +### EthEstimateGas + + +Perms: read + +Inputs: +```json +[ + { + "from": "0x0707070707070707070707070707070707070707", + "to": "0x0707070707070707070707070707070707070707", + "gas": "0x5", + "gasPrice": "0x0", + "value": "0x0", + "data": "Ynl0ZSBhcnJheQ==" + }, + "string value" +] +``` + +Response: `"0x5"` + ### EthGasPrice -Perms: +Perms: read Inputs: `null` @@ -2236,26 +2282,14 @@ Response: "extraData": "Ynl0ZSBhcnJheQ==", "mixHash": "0x0707070707070707070707070707070707070707070707070707070707070707", "nonce": "0x0707070707070707", - "baseFeePerGas": "0x5", - "transactions": { - "chainId": "0x5", - "nonce": 42, - "hash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", - "transacionIndex": "0x5", - "from": "0x0707070707070707070707070707070707070707", - "to": "0x0707070707070707070707070707070707070707", - "value": "0x0", - "type": "0x5", - "input": "Ynl0ZSBhcnJheQ==", - "gas": "0x5", - "maxFeePerGas": "0x0", - "maxPriorityFeePerGas": "0x0", - "v": "0x0", - "r": "0x0", - "s": "0x0" - } + "baseFeePerGas": "0x0", + "size": "0x5", + "transactions": [ + {} + ], + "uncles": [ + "0x0707070707070707070707070707070707070707070707070707070707070707" + ] } ``` @@ -2289,26 +2323,14 @@ Response: "extraData": "Ynl0ZSBhcnJheQ==", "mixHash": "0x0707070707070707070707070707070707070707070707070707070707070707", "nonce": "0x0707070707070707", - "baseFeePerGas": "0x5", - "transactions": { - "chainId": "0x5", - "nonce": 42, - "hash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", - "transacionIndex": "0x5", - "from": "0x0707070707070707070707070707070707070707", - "to": "0x0707070707070707070707070707070707070707", - "value": "0x0", - "type": "0x5", - "input": "Ynl0ZSBhcnJheQ==", - "gas": "0x5", - "maxFeePerGas": "0x0", - "maxPriorityFeePerGas": "0x0", - "v": "0x0", - "r": "0x0", - "s": "0x0" - } + "baseFeePerGas": "0x0", + "size": "0x5", + "transactions": [ + {} + ], + "uncles": [ + "0x0707070707070707070707070707070707070707070707070707070707070707" + ] } ``` @@ -2392,7 +2414,7 @@ Response: "nonce": 42, "hash": "0x0707070707070707070707070707070707070707070707070707070707070707", "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockNumber": "0x5", "transacionIndex": "0x5", "from": "0x0707070707070707070707070707070707070707", "to": "0x0707070707070707070707070707070707070707", @@ -2428,7 +2450,7 @@ Response: "nonce": 42, "hash": "0x0707070707070707070707070707070707070707070707070707070707070707", "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockNumber": "0x5", "transacionIndex": "0x5", "from": "0x0707070707070707070707070707070707070707", "to": "0x0707070707070707070707070707070707070707", @@ -2463,7 +2485,7 @@ Response: "nonce": 42, "hash": "0x0707070707070707070707070707070707070707070707070707070707070707", "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", - "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockNumber": "0x5", "transacionIndex": "0x5", "from": "0x0707070707070707070707070707070707070707", "to": "0x0707070707070707070707070707070707070707", diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index 5fe3eaa8b..f6cf27a7e 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" ) @@ -33,8 +34,10 @@ type EthModuleAPI interface { NetVersion(ctx context.Context) (string, error) NetListening(ctx context.Context) (bool, error) EthProtocolVersion(ctx context.Context) (api.EthInt, error) - EthMaxPriorityFeePerGas(ctx context.Context) (api.EthInt, error) EthGasPrice(ctx context.Context) (api.EthInt, error) + EthEstimateGas(ctx context.Context, tx api.EthCall, blkParam string) (api.EthInt, error) + EthCall(ctx context.Context, tx api.EthCall, blkParam string) (string, error) + EthMaxPriorityFeePerGas(ctx context.Context) (api.EthInt, error) // EthSendRawTransaction(ctx context.Context, tx api.EthTx) (api.EthHash, error) } @@ -47,6 +50,8 @@ type EthModule struct { fx.In Chain *store.ChainStore + + ChainAPI StateAPI } @@ -104,15 +109,34 @@ func (a *EthModule) EthGetBlockTransactionCountByHash(ctx context.Context, blkHa } func (a *EthModule) EthGetBlockByHash(ctx context.Context, blkHash api.EthHash, fullTxInfo bool) (api.EthBlock, error) { - return api.EthBlock{}, nil + ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid()) + if err != nil { + return api.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + return a.ethBlockFromFilecoinTipSet(ctx, ts, fullTxInfo) } func (a *EthModule) EthGetBlockByNumber(ctx context.Context, blkNum api.EthInt, fullTxInfo bool) (api.EthBlock, error) { - return api.EthBlock{}, nil + ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(blkNum), nil, false) + if err != nil { + return api.EthBlock{}, xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + return a.ethBlockFromFilecoinTipSet(ctx, ts, fullTxInfo) } func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash api.EthHash) (api.EthTx, error) { - return api.EthTx{}, nil + cid := txHash.ToCid() + + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true) + if err != nil { + return api.EthTx{}, nil + } + + tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup) + if err != nil { + return api.EthTx{}, err + } + return tx, nil } func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkParam string) (api.EthInt, error) { @@ -155,7 +179,7 @@ func (a *EthModule) EthGetBalance(ctx context.Context, address api.EthAddress, b } func (a *EthModule) EthChainId(ctx context.Context) (api.EthInt, error) { - return api.EthInt(0), nil + return api.EthInt(build.Eip155ChainId), nil } func (a *EthModule) NetVersion(ctx context.Context) (string, error) { @@ -187,11 +211,127 @@ func (a *EthModule) EthGasPrice(ctx context.Context) (api.EthInt, error) { // return api.EthHash{}, nil // } -// func (a *EthModule) EthEstimateGas(ctx context.Context, tx api.EthTx, blkParam string) (api.EthInt, error) { -// return api.EthInt(0), nil -// } -// -// func (a *EthModule) EthCall(ctx context.Context, tx api.EthTx, blkParam string) (string, error) { -// return "", nil -// } -// +func (a *EthModule) EthEstimateGas(ctx context.Context, tx api.EthCall, blkParam string) (api.EthInt, error) { + return api.EthInt(0), nil +} + +func (a *EthModule) EthCall(ctx context.Context, tx api.EthCall, blkParam string) (string, error) { + return "", nil +} + +func (a *EthModule) ethBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool) (api.EthBlock, error) { + parent, err := a.Chain.LoadTipSet(ctx, ts.Parents()) + if err != nil { + return api.EthBlock{}, err + } + parentKeyCid, err := parent.Key().Cid() + if err != nil { + return api.EthBlock{}, err + } + parentBlkHash, err := api.EthHashFromCid(parentKeyCid) + if err != nil { + return api.EthBlock{}, err + } + + blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts) + if err != nil { + return api.EthBlock{}, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err) + } + + block := api.NewEthBlock() + + // this seems to be a very expensive way to get gasUsed of the block. may need to find an efficient way to do it + gasUsed := int64(0) + for _, blkMsg := range blkMsgs { + for _, msg := range append(blkMsg.BlsMessages, blkMsg.SecpkMessages...) { + msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, msg.Cid(), api.LookbackNoLimit, true) + if err != nil { + return api.EthBlock{}, nil + } + gasUsed += msgLookup.Receipt.GasUsed + + if fullTxInfo { + tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup) + if err != nil { + return api.EthBlock{}, nil + } + block.Transactions = append(block.Transactions, tx) + } else { + hash, err := api.EthHashFromCid(msg.Cid()) + if err != nil { + return api.EthBlock{}, err + } + block.Transactions = append(block.Transactions, hash.String()) + } + } + } + + block.Number = api.EthInt(ts.Height()) + block.ParentHash = parentBlkHash + block.Timestamp = api.EthInt(ts.Blocks()[0].Timestamp) + block.BaseFeePerGas = api.EthBigInt{Int: ts.Blocks()[0].ParentBaseFee.Int} + block.GasUsed = api.EthInt(gasUsed) + return block, nil +} + +func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLookup *api.MsgLookup) (api.EthTx, error) { + cid := msgLookup.Message + txHash, err := api.EthHashFromCid(cid) + if err != nil { + return api.EthTx{}, err + } + + tsCid, err := msgLookup.TipSet.Cid() + if err != nil { + return api.EthTx{}, err + } + + blkHash, err := api.EthHashFromCid(tsCid) + if err != nil { + return api.EthTx{}, err + } + + msg, err := a.ChainAPI.ChainGetMessage(ctx, msgLookup.Message) + if err != nil { + return api.EthTx{}, err + } + + fromFilIdAddr, err := a.StateAPI.StateLookupID(ctx, msg.From, types.EmptyTSK) + if err != nil { + return api.EthTx{}, err + } + + fromEthAddr, err := api.EthAddressFromFilecoinIDAddress(fromFilIdAddr) + if err != nil { + return api.EthTx{}, err + } + + toFilAddr, err := a.StateAPI.StateLookupID(ctx, msg.From, types.EmptyTSK) + if err != nil { + return api.EthTx{}, err + } + + toEthAddr, err := api.EthAddressFromFilecoinIDAddress(toFilAddr) + if err != nil { + return api.EthTx{}, err + } + + tx := api.EthTx{ + ChainID: api.EthInt(build.Eip155ChainId), + Hash: txHash, + BlockHash: blkHash, + BlockNumber: api.EthInt(msgLookup.Height), + From: fromEthAddr, + To: toEthAddr, + Value: api.EthBigInt(msg.Value), + Type: api.EthInt(2), + Gas: api.EthInt(msg.GasLimit), + MaxFeePerGas: api.EthBigInt(msg.GasFeeCap), + MaxPriorityFeePerGas: api.EthBigInt(msg.GasPremium), + V: api.EthBigIntZero, + R: api.EthBigIntZero, + S: api.EthBigIntZero, + // TODO: Input: + } + return tx, nil +} diff --git a/node/rpc.go b/node/rpc.go index 8a2fe079c..2127101e9 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -95,8 +95,8 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server rpcServer.AliasMethod("eth_maxPriorityFeePerGas", "Filecoin.EthMaxPriorityFeePerGas") rpcServer.AliasMethod("eth_gasPrice", "Filecoin.EthGasPrice") rpcServer.AliasMethod("eth_sendRawTransaction", "Filecoin.EthSendRawTransaction") - // rpcServer.AliasMethod("eth_estimateGas", "Filecoin.EthEstimateGas") - // rpcServer.AliasMethod("eth_call", "Filecoin.EthCall") + rpcServer.AliasMethod("eth_estimateGas", "Filecoin.EthEstimateGas") + rpcServer.AliasMethod("eth_call", "Filecoin.EthCall") rpcServer.AliasMethod("net_version", "Filecoin.NetVersion") rpcServer.AliasMethod("net_listening", "Filecoin.NetListening")