feat: ethrpc: implement call, estimateGas, getTransactionCount (#9306)

This commit is contained in:
Kevin Li 2022-09-28 10:58:24 -04:00 committed by vyzo
parent e5bb5b7430
commit b53d5924a1
7 changed files with 352 additions and 90 deletions

View File

@ -774,7 +774,7 @@ type FullNode interface {
EthGetBlockByNumber(ctx context.Context, blkNum EthInt, fullTxInfo bool) (EthBlock, error) //perm:read
EthGetTransactionByHash(ctx context.Context, txHash EthHash) (EthTx, error) //perm:read
EthGetTransactionCount(ctx context.Context, sender EthAddress, blkOpt string) (EthInt, error) //perm:read
EthGetTransactionReceipt(ctx context.Context, blkHash EthHash) (EthTxReceipt, error) //perm:read
EthGetTransactionReceipt(ctx context.Context, txHash EthHash) (EthTxReceipt, error) //perm:read
EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash EthHash, txIndex EthInt) (EthTx, error) //perm:read
EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum EthInt, txIndex EthInt) (EthTx, error) //perm:read
@ -785,10 +785,13 @@ 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
EthGasPrice(ctx context.Context) (EthInt, error) //perm:read
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
EthGasPrice(ctx context.Context) (EthBigInt, error) //perm:read
EthMaxPriorityFeePerGas(ctx context.Context) (EthBigInt, error) //perm:read
EthEstimateGas(ctx context.Context, tx EthCall, blkParam string) (EthInt, error) //perm:read
EthCall(ctx context.Context, tx EthCall, blkParam string) (EthBytes, error) //perm:read
EthSendRawTransaction(ctx context.Context, rawTx EthBytes) (EthHash, error) //perm:read
// CreateBackup creates node backup onder the specified file name. The
// method requires that the lotus daemon is running with the

View File

@ -1,6 +1,7 @@
package api
import (
"bytes"
"encoding/binary"
"encoding/hex"
"encoding/json"
@ -15,6 +16,7 @@ import (
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"
init8 "github.com/filecoin-project/go-state-types/builtin/v8/init"
"github.com/filecoin-project/lotus/build"
)
@ -70,6 +72,33 @@ func (e *EthBigInt) UnmarshalJSON(b []byte) error {
return nil
}
type EthBytes []byte
func (e EthBytes) MarshalJSON() ([]byte, error) {
encoded := "0x" + hex.EncodeToString(e)
return json.Marshal(encoded)
}
func (e *EthBytes) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
s = strings.Replace(s, "0x", "", -1)
if len(s)%2 == 1 {
s = "0" + s
}
decoded, err := hex.DecodeString(s)
if err != nil {
return err
}
*e = decoded
return nil
}
type EthBlock struct {
ParentHash EthHash `json:"parentHash"`
Sha3Uncles EthHash `json:"sha3Uncles"`
@ -116,23 +145,23 @@ func NewEthBlock() EthBlock {
}
type EthTx struct {
ChainID EthInt `json:"chainId"`
Nonce uint64 `json:"nonce"`
Hash EthHash `json:"hash"`
BlockHash EthHash `json:"blockHash"`
BlockNumber EthInt `json:"blockNumber"`
TransactionIndex EthInt `json:"transacionIndex"`
From EthAddress `json:"from"`
To EthAddress `json:"to"`
Value EthBigInt `json:"value"`
Type EthInt `json:"type"`
Input []byte `json:"input"`
Gas EthInt `json:"gas"`
MaxFeePerGas EthBigInt `json:"maxFeePerGas"`
MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"`
V EthBigInt `json:"v"`
R EthBigInt `json:"r"`
S EthBigInt `json:"s"`
ChainID EthInt `json:"chainId"`
Nonce uint64 `json:"nonce"`
Hash EthHash `json:"hash"`
BlockHash EthHash `json:"blockHash"`
BlockNumber EthInt `json:"blockNumber"`
TransactionIndex EthInt `json:"transacionIndex"`
From EthAddress `json:"from"`
To *EthAddress `json:"to"`
Value EthBigInt `json:"value"`
Type EthInt `json:"type"`
Input EthBytes `json:"input"`
Gas EthInt `json:"gas"`
MaxFeePerGas EthBigInt `json:"maxFeePerGas"`
MaxPriorityFeePerGas EthBigInt `json:"maxPriorityFeePerGas"`
V EthBigInt `json:"v"`
R EthBigInt `json:"r"`
S EthBigInt `json:"s"`
}
type EthCall struct {
@ -141,7 +170,7 @@ type EthCall struct {
Gas EthInt `json:"gas"`
GasPrice EthBigInt `json:"gasPrice"`
Value EthBigInt `json:"value"`
Data []byte `json:"data"`
Data EthBytes `json:"data"`
}
func (c *EthCall) UnmarshalJSON(b []byte) error {
@ -156,22 +185,71 @@ func (c *EthCall) UnmarshalJSON(b []byte) error {
}
type EthTxReceipt struct {
TransactionHash EthHash `json:"transactionHash"`
TransactionIndex EthInt `json:"transacionIndex"`
BlockHash EthHash `json:"blockHash"`
BlockNumber EthHash `json:"blockNumber"`
From EthAddress `json:"from"`
To EthAddress `json:"to"`
TransactionHash EthHash `json:"transactionHash"`
TransactionIndex EthInt `json:"transacionIndex"`
BlockHash EthHash `json:"blockHash"`
BlockNumber EthInt `json:"blockNumber"`
From EthAddress `json:"from"`
To *EthAddress `json:"to"`
// Logs
// LogsBloom
StateRoot EthHash `json:"root"`
Status EthInt `json:"status"`
ContractAddress *EthAddress `json:"contractAddress"`
CumulativeGasUsed EthBigInt `json:"cumulativeGasUsed"`
GasUsed EthBigInt `json:"gasUsed"`
CumulativeGasUsed EthInt `json:"cumulativeGasUsed"`
GasUsed EthInt `json:"gasUsed"`
EffectiveGasPrice EthBigInt `json:"effectiveGasPrice"`
}
func NewEthTxReceipt(tx EthTx, lookup *MsgLookup, replay *InvocResult) (EthTxReceipt, error) {
receipt := EthTxReceipt{
TransactionHash: tx.Hash,
TransactionIndex: tx.TransactionIndex,
BlockHash: tx.BlockHash,
BlockNumber: tx.BlockNumber,
From: tx.From,
To: tx.To,
StateRoot: EmptyEthHash,
}
contractAddr, err := CheckContractCreation(lookup)
if err == nil {
receipt.To = nil
receipt.ContractAddress = contractAddr
}
if lookup.Receipt.ExitCode.IsSuccess() {
receipt.Status = 1
}
if lookup.Receipt.ExitCode.IsError() {
receipt.Status = 0
}
receipt.GasUsed = EthInt(lookup.Receipt.GasUsed)
// TODO: handle CumulativeGasUsed
receipt.CumulativeGasUsed = EmptyEthInt
effectiveGasPrice := big.Div(replay.GasCost.TotalCost, big.NewInt(lookup.Receipt.GasUsed))
receipt.EffectiveGasPrice = EthBigInt(effectiveGasPrice)
return receipt, nil
}
func CheckContractCreation(lookup *MsgLookup) (*EthAddress, error) {
if lookup.Receipt.ExitCode.IsError() {
return nil, xerrors.Errorf("message execution was not successful")
}
var result init8.ExecReturn
ret := bytes.NewReader(lookup.Receipt.Return)
if err := result.UnmarshalCBOR(ret); err == nil {
contractAddr, err := EthAddressFromFilecoinIDAddress(result.IDAddress)
if err == nil {
return &contractAddr, nil
}
}
return nil, xerrors.Errorf("not a contract creation tx")
}
const (
ETH_ADDRESS_LENGTH = 20
ETH_HASH_LENGTH = 32

View File

@ -136,3 +136,22 @@ func TestUnmarshalEthCall(t *testing.T) {
err := c.UnmarshalJSON([]byte(data))
require.Nil(t, err)
}
func TestUnmarshalEthBytes(t *testing.T) {
testcases := []string{
`"0x00"`,
strings.ToLower(`"0xd4c5fb16488Aa48081296299d54b0c648C9333dA"`),
strings.ToLower(`"0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"`),
strings.ToLower(`"0x01184F793982104363F9a8a5845743f452dE0586"`),
}
for _, tc := range testcases {
var s EthBytes
err := s.UnmarshalJSON([]byte(tc))
require.Nil(t, err)
data, err := s.MarshalJSON()
require.Nil(t, err)
require.Equal(t, string(data), tc)
}
}

View File

@ -952,10 +952,10 @@ func (mr *MockFullNodeMockRecorder) EthBlockNumber(arg0 interface{}) *gomock.Cal
}
// EthCall mocks base method.
func (m *MockFullNode) EthCall(arg0 context.Context, arg1 api.EthCall, arg2 string) (string, error) {
func (m *MockFullNode) EthCall(arg0 context.Context, arg1 api.EthCall, arg2 string) (api.EthBytes, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthCall", arg0, arg1, arg2)
ret0, _ := ret[0].(string)
ret0, _ := ret[0].(api.EthBytes)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -997,10 +997,10 @@ func (mr *MockFullNodeMockRecorder) EthEstimateGas(arg0, arg1, arg2 interface{})
}
// EthGasPrice mocks base method.
func (m *MockFullNode) EthGasPrice(arg0 context.Context) (api.EthInt, error) {
func (m *MockFullNode) EthGasPrice(arg0 context.Context) (api.EthBigInt, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthGasPrice", arg0)
ret0, _ := ret[0].(api.EthInt)
ret0, _ := ret[0].(api.EthBigInt)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -1192,10 +1192,10 @@ func (mr *MockFullNodeMockRecorder) EthGetTransactionReceipt(arg0, arg1 interfac
}
// EthMaxPriorityFeePerGas mocks base method.
func (m *MockFullNode) EthMaxPriorityFeePerGas(arg0 context.Context) (api.EthInt, error) {
func (m *MockFullNode) EthMaxPriorityFeePerGas(arg0 context.Context) (api.EthBigInt, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthMaxPriorityFeePerGas", arg0)
ret0, _ := ret[0].(api.EthInt)
ret0, _ := ret[0].(api.EthBigInt)
ret1, _ := ret[1].(error)
return ret0, ret1
}
@ -1221,6 +1221,21 @@ func (mr *MockFullNodeMockRecorder) EthProtocolVersion(arg0 interface{}) *gomock
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthProtocolVersion", reflect.TypeOf((*MockFullNode)(nil).EthProtocolVersion), arg0)
}
// EthSendRawTransaction mocks base method.
func (m *MockFullNode) EthSendRawTransaction(arg0 context.Context, arg1 api.EthBytes) (api.EthHash, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "EthSendRawTransaction", arg0, arg1)
ret0, _ := ret[0].(api.EthHash)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// EthSendRawTransaction indicates an expected call of EthSendRawTransaction.
func (mr *MockFullNodeMockRecorder) EthSendRawTransaction(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthSendRawTransaction", reflect.TypeOf((*MockFullNode)(nil).EthSendRawTransaction), arg0, arg1)
}
// GasEstimateFeeCap mocks base method.
func (m *MockFullNode) GasEstimateFeeCap(arg0 context.Context, arg1 *types.Message, arg2 int64, arg3 types.TipSetKey) (big.Int, error) {
m.ctrl.T.Helper()

View File

@ -222,13 +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"`
EthCall func(p0 context.Context, p1 EthCall, p2 string) (EthBytes, error) `perm:"read"`
EthChainId func(p0 context.Context) (EthInt, error) `perm:"read"`
EthEstimateGas func(p0 context.Context, p1 EthCall, p2 string) (EthInt, error) `perm:"read"`
EthGasPrice func(p0 context.Context) (EthInt, error) `perm:"read"`
EthGasPrice func(p0 context.Context) (EthBigInt, error) `perm:"read"`
EthGetBalance func(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) `perm:"read"`
@ -254,10 +254,12 @@ type FullNodeStruct struct {
EthGetTransactionReceipt func(p0 context.Context, p1 EthHash) (EthTxReceipt, error) `perm:"read"`
EthMaxPriorityFeePerGas func(p0 context.Context) (EthInt, error) `perm:"read"`
EthMaxPriorityFeePerGas func(p0 context.Context) (EthBigInt, error) `perm:"read"`
EthProtocolVersion func(p0 context.Context) (EthInt, error) `perm:"read"`
EthSendRawTransaction func(p0 context.Context, p1 EthBytes) (EthHash, error) `perm:"read"`
GasEstimateFeeCap func(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) `perm:"read"`
GasEstimateGasLimit func(p0 context.Context, p1 *types.Message, p2 types.TipSetKey) (int64, error) `perm:"read"`
@ -1857,15 +1859,15 @@ 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) {
func (s *FullNodeStruct) EthCall(p0 context.Context, p1 EthCall, p2 string) (EthBytes, error) {
if s.Internal.EthCall == nil {
return "", ErrNotSupported
return *new(EthBytes), 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 *FullNodeStub) EthCall(p0 context.Context, p1 EthCall, p2 string) (EthBytes, error) {
return *new(EthBytes), ErrNotSupported
}
func (s *FullNodeStruct) EthChainId(p0 context.Context) (EthInt, error) {
@ -1890,15 +1892,15 @@ func (s *FullNodeStub) EthEstimateGas(p0 context.Context, p1 EthCall, p2 string)
return *new(EthInt), ErrNotSupported
}
func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (EthInt, error) {
func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (EthBigInt, error) {
if s.Internal.EthGasPrice == nil {
return *new(EthInt), ErrNotSupported
return *new(EthBigInt), ErrNotSupported
}
return s.Internal.EthGasPrice(p0)
}
func (s *FullNodeStub) EthGasPrice(p0 context.Context) (EthInt, error) {
return *new(EthInt), ErrNotSupported
func (s *FullNodeStub) EthGasPrice(p0 context.Context) (EthBigInt, error) {
return *new(EthBigInt), ErrNotSupported
}
func (s *FullNodeStruct) EthGetBalance(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) {
@ -2033,15 +2035,15 @@ func (s *FullNodeStub) EthGetTransactionReceipt(p0 context.Context, p1 EthHash)
return *new(EthTxReceipt), ErrNotSupported
}
func (s *FullNodeStruct) EthMaxPriorityFeePerGas(p0 context.Context) (EthInt, error) {
func (s *FullNodeStruct) EthMaxPriorityFeePerGas(p0 context.Context) (EthBigInt, error) {
if s.Internal.EthMaxPriorityFeePerGas == nil {
return *new(EthInt), ErrNotSupported
return *new(EthBigInt), ErrNotSupported
}
return s.Internal.EthMaxPriorityFeePerGas(p0)
}
func (s *FullNodeStub) EthMaxPriorityFeePerGas(p0 context.Context) (EthInt, error) {
return *new(EthInt), ErrNotSupported
func (s *FullNodeStub) EthMaxPriorityFeePerGas(p0 context.Context) (EthBigInt, error) {
return *new(EthBigInt), ErrNotSupported
}
func (s *FullNodeStruct) EthProtocolVersion(p0 context.Context) (EthInt, error) {
@ -2055,6 +2057,17 @@ func (s *FullNodeStub) EthProtocolVersion(p0 context.Context) (EthInt, error) {
return *new(EthInt), ErrNotSupported
}
func (s *FullNodeStruct) EthSendRawTransaction(p0 context.Context, p1 EthBytes) (EthHash, error) {
if s.Internal.EthSendRawTransaction == nil {
return *new(EthHash), ErrNotSupported
}
return s.Internal.EthSendRawTransaction(p0, p1)
}
func (s *FullNodeStub) EthSendRawTransaction(p0 context.Context, p1 EthBytes) (EthHash, error) {
return *new(EthHash), ErrNotSupported
}
func (s *FullNodeStruct) GasEstimateFeeCap(p0 context.Context, p1 *types.Message, p2 int64, p3 types.TipSetKey) (types.BigInt, error) {
if s.Internal.GasEstimateFeeCap == nil {
return *new(types.BigInt), ErrNotSupported

View File

@ -86,6 +86,7 @@
* [EthGetTransactionReceipt](#EthGetTransactionReceipt)
* [EthMaxPriorityFeePerGas](#EthMaxPriorityFeePerGas)
* [EthProtocolVersion](#EthProtocolVersion)
* [EthSendRawTransaction](#EthSendRawTransaction)
* [Gas](#Gas)
* [GasEstimateFeeCap](#GasEstimateFeeCap)
* [GasEstimateGasLimit](#GasEstimateGasLimit)
@ -2189,13 +2190,13 @@ Inputs:
"gas": "0x5",
"gasPrice": "0x0",
"value": "0x0",
"data": "Ynl0ZSBhcnJheQ=="
"data": "0x07"
},
"string value"
]
```
Response: `"string value"`
Response: `"0x07"`
### EthChainId
@ -2220,7 +2221,7 @@ Inputs:
"gas": "0x5",
"gasPrice": "0x0",
"value": "0x0",
"data": "Ynl0ZSBhcnJheQ=="
"data": "0x07"
},
"string value"
]
@ -2235,7 +2236,7 @@ Perms: read
Inputs: `null`
Response: `"0x5"`
Response: `"0x0"`
### EthGetBalance
@ -2417,10 +2418,10 @@ Response:
"blockNumber": "0x5",
"transacionIndex": "0x5",
"from": "0x0707070707070707070707070707070707070707",
"to": "0x0707070707070707070707070707070707070707",
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
"value": "0x0",
"type": "0x5",
"input": "Ynl0ZSBhcnJheQ==",
"input": "0x07",
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
@ -2453,10 +2454,10 @@ Response:
"blockNumber": "0x5",
"transacionIndex": "0x5",
"from": "0x0707070707070707070707070707070707070707",
"to": "0x0707070707070707070707070707070707070707",
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
"value": "0x0",
"type": "0x5",
"input": "Ynl0ZSBhcnJheQ==",
"input": "0x07",
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
@ -2488,10 +2489,10 @@ Response:
"blockNumber": "0x5",
"transacionIndex": "0x5",
"from": "0x0707070707070707070707070707070707070707",
"to": "0x0707070707070707070707070707070707070707",
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
"value": "0x0",
"type": "0x5",
"input": "Ynl0ZSBhcnJheQ==",
"input": "0x07",
"gas": "0x5",
"maxFeePerGas": "0x0",
"maxPriorityFeePerGas": "0x0",
@ -2534,14 +2535,14 @@ Response:
"transactionHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"transacionIndex": "0x5",
"blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707",
"blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707",
"blockNumber": "0x5",
"from": "0x0707070707070707070707070707070707070707",
"to": "0x0707070707070707070707070707070707070707",
"to": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
"root": "0x0707070707070707070707070707070707070707070707070707070707070707",
"status": "0x5",
"contractAddress": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031",
"cumulativeGasUsed": "0x0",
"gasUsed": "0x0",
"cumulativeGasUsed": "0x5",
"gasUsed": "0x5",
"effectiveGasPrice": "0x0"
}
```
@ -2553,7 +2554,7 @@ Perms: read
Inputs: `null`
Response: `"0x5"`
Response: `"0x0"`
### EthProtocolVersion
@ -2564,6 +2565,20 @@ Inputs: `null`
Response: `"0x5"`
### EthSendRawTransaction
Perms: read
Inputs:
```json
[
"0x07"
]
```
Response: `"0x0707070707070707070707070707070707070707070707070707070707070707"`
## Gas

View File

@ -8,11 +8,17 @@ import (
"golang.org/x/xerrors"
"github.com/filecoin-project/go-state-types/abi"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/exitcode"
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/messagepool"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/node/modules/dtypes"
)
type EthModuleAPI interface {
@ -24,7 +30,7 @@ type EthModuleAPI interface {
EthGetBlockByNumber(ctx context.Context, blkNum api.EthInt, fullTxInfo bool) (api.EthBlock, error)
EthGetTransactionByHash(ctx context.Context, txHash api.EthHash) (api.EthTx, error)
EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkOpt string) (api.EthInt, error)
EthGetTransactionReceipt(ctx context.Context, blkHash api.EthHash) (api.EthTxReceipt, error)
EthGetTransactionReceipt(ctx context.Context, txHash api.EthHash) (api.EthTxReceipt, error)
EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash api.EthHash, txIndex api.EthInt) (api.EthTx, error)
EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum api.EthInt, txIndex api.EthInt) (api.EthTx, error)
EthGetCode(ctx context.Context, address api.EthAddress) (string, error)
@ -34,11 +40,11 @@ type EthModuleAPI interface {
NetVersion(ctx context.Context) (string, error)
NetListening(ctx context.Context) (bool, error)
EthProtocolVersion(ctx context.Context) (api.EthInt, error)
EthGasPrice(ctx context.Context) (api.EthInt, error)
EthGasPrice(ctx context.Context) (api.EthBigInt, 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)
EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error)
EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error)
EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error)
}
var _ EthModuleAPI = *new(api.FullNode)
@ -49,9 +55,12 @@ var _ EthModuleAPI = *new(api.FullNode)
type EthModule struct {
fx.In
Chain *store.ChainStore
Chain *store.ChainStore
Mpool *messagepool.MessagePool
StateManager *stmgr.StateManager
ChainAPI
MpoolAPI
StateAPI
}
@ -65,6 +74,10 @@ type EthAPI struct {
EthModuleAPI
}
func (a *EthModule) StateNetworkName(ctx context.Context) (dtypes.NetworkName, error) {
return stmgr.GetNetworkName(ctx, a.StateManager, a.Chain.GetHeaviestTipSet().ParentState())
}
func (a *EthModule) EthBlockNumber(context.Context) (api.EthInt, error) {
height := a.Chain.GetHeaviestTipSet().Height()
return api.EthInt(height), nil
@ -140,11 +153,40 @@ func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash api.EthH
}
func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkParam string) (api.EthInt, error) {
return api.EthInt(0), nil
addr, err := sender.ToFilecoinAddress()
if err != nil {
return api.EthInt(0), err
}
nonce, err := a.Mpool.GetNonce(ctx, addr, types.EmptyTSK)
if err != nil {
return api.EthInt(0), err
}
return api.EthInt(nonce), nil
}
func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, blkHash api.EthHash) (api.EthTxReceipt, error) {
return api.EthTxReceipt{}, nil
func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, txHash api.EthHash) (api.EthTxReceipt, error) {
cid := txHash.ToCid()
msgLookup, err := a.StateAPI.StateSearchMsg(ctx, types.EmptyTSK, cid, api.LookbackNoLimit, true)
if err != nil {
return api.EthTxReceipt{}, err
}
tx, err := a.ethTxFromFilecoinMessageLookup(ctx, msgLookup)
if err != nil {
return api.EthTxReceipt{}, err
}
replay, err := a.StateAPI.StateReplay(ctx, types.EmptyTSK, cid)
if err != nil {
return api.EthTxReceipt{}, err
}
receipt, err := api.NewEthTxReceipt(tx, msgLookup, replay)
if err != nil {
return api.EthTxReceipt{}, err
}
return receipt, nil
}
func (a *EthModule) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash api.EthHash, txIndex api.EthInt) (api.EthTx, error) {
@ -196,27 +238,98 @@ func (a *EthModule) NetListening(ctx context.Context) (bool, error) {
}
func (a *EthModule) EthProtocolVersion(ctx context.Context) (api.EthInt, error) {
return api.EthInt(0), nil
height := a.Chain.GetHeaviestTipSet().Height()
return api.EthInt(a.StateManager.GetNetworkVersion(ctx, height)), nil
}
func (a *EthModule) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthInt, error) {
return api.EthInt(0), nil
func (a *EthModule) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthBigInt, error) {
gasPremium, err := a.GasAPI.GasEstimateGasPremium(ctx, 0, builtin.SystemActorAddr, 10000, types.EmptyTSK)
if err != nil {
return api.EthBigInt(big.Zero()), err
}
return api.EthBigInt(gasPremium), nil
}
func (a *EthModule) EthGasPrice(ctx context.Context) (api.EthInt, error) {
return api.EthInt(0), nil
func (a *EthModule) EthGasPrice(ctx context.Context) (api.EthBigInt, error) {
// According to Geth's implementation, eth_gasPrice should return base + tip
// Ref: https://github.com/ethereum/pm/issues/328#issuecomment-853234014
ts := a.Chain.GetHeaviestTipSet()
baseFee := ts.Blocks()[0].ParentBaseFee
premium, err := a.EthMaxPriorityFeePerGas(ctx)
if err != nil {
return api.EthBigInt(big.Zero()), nil
}
gasPrice := big.Add(baseFee, big.Int(premium))
return api.EthBigInt(gasPrice), nil
}
// func (a *EthModule) EthSendRawTransaction(ctx context.Context tx api.EthTx) (api.EthHash, error) {
// return api.EthHash{}, nil
// }
func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx api.EthBytes) (api.EthHash, error) {
return api.EthHash{}, nil
}
func (a *EthModule) applyEvmMsg(ctx context.Context, tx api.EthCall) (*api.InvocResult, error) {
from, err := tx.From.ToFilecoinAddress()
if err != nil {
return nil, err
}
to, err := tx.To.ToFilecoinAddress()
if err != nil {
return nil, xerrors.Errorf("cannot get Filecoin address: %w", err)
}
msg := &types.Message{
From: from,
To: to,
Value: big.Int(tx.Value),
Method: abi.MethodNum(2),
Params: tx.Data,
GasLimit: build.BlockGasLimit,
GasFeeCap: big.Zero(),
GasPremium: big.Zero(),
}
ts := a.Chain.GetHeaviestTipSet()
// Try calling until we find a height with no migration.
var res *api.InvocResult
for {
res, err = a.StateManager.CallWithGas(ctx, msg, []types.ChainMsg{}, ts)
if err != stmgr.ErrExpensiveFork {
break
}
ts, err = a.Chain.GetTipSetFromKey(ctx, ts.Parents())
if err != nil {
return nil, xerrors.Errorf("getting parent tipset: %w", err)
}
}
if err != nil {
return nil, xerrors.Errorf("CallWithGas failed: %w", err)
}
if res.MsgRct.ExitCode != exitcode.Ok {
return nil, xerrors.Errorf("message execution failed: exit %s, reason: %s", res.MsgRct.ExitCode, res.Error)
}
return res, nil
}
func (a *EthModule) EthEstimateGas(ctx context.Context, tx api.EthCall, blkParam string) (api.EthInt, error) {
return api.EthInt(0), nil
invokeResult, err := a.applyEvmMsg(ctx, tx)
if err != nil {
return api.EthInt(0), err
}
ret := invokeResult.MsgRct.GasUsed
return api.EthInt(ret), nil
}
func (a *EthModule) EthCall(ctx context.Context, tx api.EthCall, blkParam string) (string, error) {
return "", nil
func (a *EthModule) EthCall(ctx context.Context, tx api.EthCall, blkParam string) (api.EthBytes, error) {
invokeResult, err := a.applyEvmMsg(ctx, tx)
if err != nil {
return nil, err
}
if len(invokeResult.MsgRct.Return) > 0 {
return api.EthBytes(invokeResult.MsgRct.Return), nil
}
return api.EthBytes{}, nil
}
func (a *EthModule) ethBlockFromFilecoinTipSet(ctx context.Context, ts *types.TipSet, fullTxInfo bool) (api.EthBlock, error) {
@ -306,7 +419,7 @@ func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLooku
return api.EthTx{}, err
}
toFilAddr, err := a.StateAPI.StateLookupID(ctx, msg.From, types.EmptyTSK)
toFilAddr, err := a.StateAPI.StateLookupID(ctx, msg.To, types.EmptyTSK)
if err != nil {
return api.EthTx{}, err
}
@ -316,13 +429,19 @@ func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLooku
return api.EthTx{}, err
}
toAddr := &toEthAddr
_, err = api.CheckContractCreation(msgLookup)
if err == nil {
toAddr = nil
}
tx := api.EthTx{
ChainID: api.EthInt(build.Eip155ChainId),
Hash: txHash,
BlockHash: blkHash,
BlockNumber: api.EthInt(msgLookup.Height),
From: fromEthAddr,
To: toEthAddr,
To: toAddr,
Value: api.EthBigInt(msg.Value),
Type: api.EthInt(2),
Gas: api.EthInt(msg.GasLimit),
@ -331,7 +450,7 @@ func (a *EthModule) ethTxFromFilecoinMessageLookup(ctx context.Context, msgLooku
V: api.EthBigIntZero,
R: api.EthBigIntZero,
S: api.EthBigIntZero,
// TODO: Input:
Input: msg.Params,
}
return tx, nil
}