diff --git a/api/api_full.go b/api/api_full.go index 320a20687..d9ad75652 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -758,6 +758,37 @@ type FullNode interface { NodeStatus(ctx context.Context, inclChainStatus bool) (NodeStatus, error) //perm:read + // MethodGroup: Eth + // These methods are used for Ethereum-compatible JSON-RPC calls + // + // EthAccounts will always return [] since we don't expect Lotus to manage private keys + EthAccounts(ctx context.Context) ([]EthAddress, error) //perm:read + // EthBlockNumber returns the height of the latest (heaviest) TipSet + EthBlockNumber(ctx context.Context) (EthInt, error) //perm:read + // EthGetBlockTransactionCountByNumber returns the number of messages in the TipSet + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum EthInt) (EthInt, error) //perm:read + // EthGetBlockTransactionCountByHash returns the number of messages in the TipSet + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash EthHash) (EthInt, error) //perm:read + + EthGetBlockByHash(ctx context.Context, blkHash EthHash, fullTxInfo bool) (EthBlock, error) //perm:read + 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 + EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash EthHash, txIndex EthInt) (EthTx, error) //perm:read + EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum EthInt, txIndex EthInt) (EthTx, error) //perm:read + + EthGetCode(ctx context.Context, address EthAddress) (string, error) //perm:read + EthGetStorageAt(ctx context.Context, address EthAddress, position EthInt, blkParam string) (string, error) //perm:read + EthGetBalance(ctx context.Context, address EthAddress, blkParam string) (EthBigInt, error) //perm:read + EthChainId(ctx context.Context) (EthInt, error) //perm:read + 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 + // CreateBackup creates node backup onder the specified file name. The // method requires that the lotus daemon is running with the // LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that diff --git a/api/eth_types.go b/api/eth_types.go new file mode 100644 index 000000000..ba39b161e --- /dev/null +++ b/api/eth_types.go @@ -0,0 +1,239 @@ +package api + +import ( + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/ipfs/go-cid" + "github.com/multiformats/go-multihash" + xerrors "golang.org/x/xerrors" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" +) + +type EthInt int64 + +func (e EthInt) MarshalJSON() ([]byte, error) { + return json.Marshal(fmt.Sprintf("0x%x", e)) +} + +func (e *EthInt) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + parsedInt, err := strconv.ParseInt(strings.Replace(s, "0x", "", -1), 16, 64) + if err != nil { + return err + } + eint := EthInt(parsedInt) + e = &eint + return nil +} + +type EthBigInt big.Int + +func (e EthBigInt) MarshalJSON() ([]byte, error) { + if e.Int == nil { + return json.Marshal("0x0") + } + return json.Marshal(fmt.Sprintf("0x%x", e.Int)) +} + +type EthBlock struct { + ParentHash EthHash `json:"parentHash"` + Sha3Uncles EthHash `json:"sha3Uncles"` + Miner EthAddress `json:"miner"` + StateRoot EthHash `json:"stateRoot"` + 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"` +} + +type EthTx struct { + ChainID *EthInt `json:"chainId"` + Nonce uint64 `json:"nonce"` + Hash EthHash `json:"hash"` + BlockHash EthHash `json:"blockHash"` + BlockNumber EthHash `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"` +} + +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"` + // Logs + // LogsBloom + StateRoot EthHash `json:"root"` + Status EthInt `json:"status"` + ContractAddress *EthAddress `json:"contractAddress"` + CumulativeGasUsed EthBigInt `json:"cumulativeGasUsed"` + GasUsed EthBigInt `json:"gasUsed"` + EffectiveGasPrice EthBigInt `json:"effectiveGasPrice"` +} + +const ( + ETH_ADDRESS_LENGTH = 20 + ETH_HASH_LENGTH = 32 +) + +type EthNonce [8]byte + +func (n EthNonce) String() string { + return "0x" + hex.EncodeToString(n[:]) +} + +func (n EthNonce) MarshalJSON() ([]byte, error) { + return json.Marshal((n.String())) +} + +type EthAddress [ETH_ADDRESS_LENGTH]byte + +func (a EthAddress) String() string { + return "0x" + hex.EncodeToString(a[:]) +} + +func (a EthAddress) MarshalJSON() ([]byte, error) { + return json.Marshal((a.String())) +} + +func (a *EthAddress) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + addr, err := EthAddressFromHex(s) + if err != nil { + return err + } + a = &addr + return nil +} + +func (a EthAddress) ToFilecoinAddress() (address.Address, error) { + id := binary.BigEndian.Uint64(a[12:]) + return address.NewIDAddress(id) +} + +func EthAddressFromFilecoinIDAddress(addr address.Address) (EthAddress, error) { + id, err := address.IDFromAddress(addr) + if err != nil { + return EthAddress{}, err + } + buf := make([]byte, ETH_ADDRESS_LENGTH) + buf[0] = 0xff + binary.BigEndian.PutUint64(buf[12:], id) + + var ethaddr EthAddress + copy(ethaddr[:], buf) + return ethaddr, nil +} + +func EthAddressFromHex(s string) (EthAddress, error) { + handlePrefix(&s) + b, err := decodeHexString(s, ETH_ADDRESS_LENGTH) + if err != nil { + return EthAddress{}, err + } + var h EthAddress + copy(h[ETH_ADDRESS_LENGTH-len(b):], b) + return h, nil +} + +type EthHash [ETH_HASH_LENGTH]byte + +func (h EthHash) MarshalJSON() ([]byte, error) { + return json.Marshal(h.String()) +} + +func (h *EthHash) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + hash, err := EthHashFromHex(s) + if err != nil { + return err + } + h = &hash + return nil +} + +func handlePrefix(s *string) { + if strings.HasPrefix(*s, "0x") || strings.HasPrefix(*s, "0X") { + *s = (*s)[2:] + } + if len(*s)%2 == 1 { + *s = "0" + *s + } +} + +func decodeHexString(s string, length int) ([]byte, error) { + b, err := hex.DecodeString(s) + + if err != nil { + return []byte{}, xerrors.Errorf("cannot parse hash: %w", err) + } + + if len(b) > length { + return []byte{}, xerrors.Errorf("length of decoded bytes is longer than %d", length) + } + + return b, nil +} + +func EthHashFromCid(c cid.Cid) (EthHash, error) { + return EthHashFromHex(c.Hash().HexString()[8:]) +} + +func EthHashFromHex(s string) (EthHash, error) { + handlePrefix(&s) + b, err := decodeHexString(s, ETH_HASH_LENGTH) + if err != nil { + return EthHash{}, err + } + var h EthHash + copy(h[ETH_HASH_LENGTH-len(b):], b) + return h, nil +} + +func (h EthHash) String() string { + return "0x" + hex.EncodeToString(h[:]) +} + +func (h EthHash) ToCid() cid.Cid { + // err is always nil + mh, _ := multihash.EncodeName(h[:], "blake2b-256") + + return cid.NewCidV1(cid.DagCBOR, mh) +} diff --git a/api/eth_types_test.go b/api/eth_types_test.go new file mode 100644 index 000000000..a29c0a06a --- /dev/null +++ b/api/eth_types_test.go @@ -0,0 +1,96 @@ +//stm: #unit +package api + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/big" +) + +type TestCase struct { + Input interface{} + Output interface{} +} + +func TestEthIntMarshalJSON(t *testing.T) { + // https://ethereum.org/en/developers/docs/apis/json-rpc/#quantities-encoding + testcases := []TestCase{ + {EthInt(0), []byte("\"0x0\"")}, + {EthInt(65), []byte("\"0x41\"")}, + {EthInt(1024), []byte("\"0x400\"")}, + } + + for _, tc := range testcases { + j, err := tc.Input.(EthInt).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthBigIntMarshalJSON(t *testing.T) { + testcases := []TestCase{ + {EthBigInt(big.NewInt(0)), []byte("\"0x0\"")}, + {EthBigInt(big.NewInt(65)), []byte("\"0x41\"")}, + {EthBigInt(big.NewInt(1024)), []byte("\"0x400\"")}, + {EthBigInt(big.Int{}), []byte("\"0x0\"")}, + } + for _, tc := range testcases { + j, err := tc.Input.(EthBigInt).MarshalJSON() + require.Nil(t, err) + require.Equal(t, j, tc.Output) + } +} + +func TestEthHash(t *testing.T) { + testcases := []string{ + "0x013dbb9442ca9667baccc6230fcd5c1c4b2d4d2870f4bd20681d4d47cfd15184", + "0xab8653edf9f51785664a643b47605a7ba3d917b5339a0724e7642c114d0e4738", + } + + for _, hash := range testcases { + h, err := EthHashFromHex(hash) + require.Nil(t, err) + require.Equal(t, h.String(), hash) + + c := h.ToCid() + h1, err := EthHashFromCid(c) + require.Nil(t, err) + require.Equal(t, h, h1) + } +} + +func TestEthAddr(t *testing.T) { + testcases := []string{ + strings.ToLower("0xd4c5fb16488Aa48081296299d54b0c648C9333dA"), + strings.ToLower("0x2C2EC67e3e1FeA8e4A39601cB3A3Cd44f5fa830d"), + strings.ToLower("0x01184F793982104363F9a8a5845743f452dE0586"), + } + + for _, addr := range testcases { + a, err := EthAddressFromHex(addr) + require.Nil(t, err) + require.Equal(t, a.String(), addr) + } +} + +func TestParseEthAddr(t *testing.T) { + testcases := []uint64{ + 1, 2, 3, 100, 101, + } + for _, id := range testcases { + addr, err := address.NewIDAddress(id) + require.Nil(t, err) + + eaddr, err := EthAddressFromFilecoinIDAddress(addr) + require.Nil(t, err) + + faddr, err := eaddr.ToFilecoinAddress() + require.Nil(t, err) + + require.Equal(t, addr, faddr) + } +} diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index f6ab05def..5b3918b95 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -921,6 +921,276 @@ func (mr *MockFullNodeMockRecorder) Discover(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Discover", reflect.TypeOf((*MockFullNode)(nil).Discover), arg0) } +// EthAccounts mocks base method. +func (m *MockFullNode) EthAccounts(arg0 context.Context) ([]api.EthAddress, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthAccounts", arg0) + ret0, _ := ret[0].([]api.EthAddress) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthAccounts indicates an expected call of EthAccounts. +func (mr *MockFullNodeMockRecorder) EthAccounts(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthAccounts", reflect.TypeOf((*MockFullNode)(nil).EthAccounts), arg0) +} + +// EthBlockNumber mocks base method. +func (m *MockFullNode) EthBlockNumber(arg0 context.Context) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthBlockNumber", arg0) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthBlockNumber indicates an expected call of EthBlockNumber. +func (mr *MockFullNodeMockRecorder) EthBlockNumber(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthBlockNumber", reflect.TypeOf((*MockFullNode)(nil).EthBlockNumber), arg0) +} + +// EthChainId mocks base method. +func (m *MockFullNode) EthChainId(arg0 context.Context) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthChainId", arg0) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthChainId indicates an expected call of EthChainId. +func (mr *MockFullNodeMockRecorder) EthChainId(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthChainId", reflect.TypeOf((*MockFullNode)(nil).EthChainId), arg0) +} + +// EthGasPrice mocks base method. +func (m *MockFullNode) EthGasPrice(arg0 context.Context) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGasPrice", arg0) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGasPrice indicates an expected call of EthGasPrice. +func (mr *MockFullNodeMockRecorder) EthGasPrice(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGasPrice", reflect.TypeOf((*MockFullNode)(nil).EthGasPrice), arg0) +} + +// EthGetBalance mocks base method. +func (m *MockFullNode) EthGetBalance(arg0 context.Context, arg1 api.EthAddress, arg2 string) (api.EthBigInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBalance", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthBigInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBalance indicates an expected call of EthGetBalance. +func (mr *MockFullNodeMockRecorder) EthGetBalance(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBalance", reflect.TypeOf((*MockFullNode)(nil).EthGetBalance), arg0, arg1, arg2) +} + +// EthGetBlockByHash mocks base method. +func (m *MockFullNode) EthGetBlockByHash(arg0 context.Context, arg1 api.EthHash, arg2 bool) (api.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByHash", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByHash indicates an expected call of EthGetBlockByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockByHash(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByHash), arg0, arg1, arg2) +} + +// EthGetBlockByNumber mocks base method. +func (m *MockFullNode) EthGetBlockByNumber(arg0 context.Context, arg1 api.EthInt, arg2 bool) (api.EthBlock, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockByNumber", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthBlock) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockByNumber indicates an expected call of EthGetBlockByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockByNumber(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockByNumber), arg0, arg1, arg2) +} + +// EthGetBlockTransactionCountByHash mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByHash(arg0 context.Context, arg1 api.EthHash) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByHash", arg0, arg1) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByHash indicates an expected call of EthGetBlockTransactionCountByHash. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByHash), arg0, arg1) +} + +// EthGetBlockTransactionCountByNumber mocks base method. +func (m *MockFullNode) EthGetBlockTransactionCountByNumber(arg0 context.Context, arg1 api.EthInt) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetBlockTransactionCountByNumber", arg0, arg1) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetBlockTransactionCountByNumber indicates an expected call of EthGetBlockTransactionCountByNumber. +func (mr *MockFullNodeMockRecorder) EthGetBlockTransactionCountByNumber(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetBlockTransactionCountByNumber", reflect.TypeOf((*MockFullNode)(nil).EthGetBlockTransactionCountByNumber), arg0, arg1) +} + +// EthGetCode mocks base method. +func (m *MockFullNode) EthGetCode(arg0 context.Context, arg1 api.EthAddress) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetCode", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetCode indicates an expected call of EthGetCode. +func (mr *MockFullNodeMockRecorder) EthGetCode(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetCode", reflect.TypeOf((*MockFullNode)(nil).EthGetCode), arg0, arg1) +} + +// EthGetStorageAt mocks base method. +func (m *MockFullNode) EthGetStorageAt(arg0 context.Context, arg1 api.EthAddress, arg2 api.EthInt, arg3 string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetStorageAt", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetStorageAt indicates an expected call of EthGetStorageAt. +func (mr *MockFullNodeMockRecorder) EthGetStorageAt(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetStorageAt", reflect.TypeOf((*MockFullNode)(nil).EthGetStorageAt), arg0, arg1, arg2, arg3) +} + +// EthGetTransactionByBlockHashAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockHashAndIndex(arg0 context.Context, arg1 api.EthHash, arg2 api.EthInt) (api.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockHashAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockHashAndIndex indicates an expected call of EthGetTransactionByBlockHashAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockHashAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockHashAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockHashAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByBlockNumberAndIndex mocks base method. +func (m *MockFullNode) EthGetTransactionByBlockNumberAndIndex(arg0 context.Context, arg1, arg2 api.EthInt) (api.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByBlockNumberAndIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByBlockNumberAndIndex indicates an expected call of EthGetTransactionByBlockNumberAndIndex. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByBlockNumberAndIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByBlockNumberAndIndex", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByBlockNumberAndIndex), arg0, arg1, arg2) +} + +// EthGetTransactionByHash mocks base method. +func (m *MockFullNode) EthGetTransactionByHash(arg0 context.Context, arg1 api.EthHash) (api.EthTx, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionByHash", arg0, arg1) + ret0, _ := ret[0].(api.EthTx) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionByHash indicates an expected call of EthGetTransactionByHash. +func (mr *MockFullNodeMockRecorder) EthGetTransactionByHash(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionByHash", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionByHash), arg0, arg1) +} + +// EthGetTransactionCount mocks base method. +func (m *MockFullNode) EthGetTransactionCount(arg0 context.Context, arg1 api.EthAddress, arg2 string) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionCount", arg0, arg1, arg2) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionCount indicates an expected call of EthGetTransactionCount. +func (mr *MockFullNodeMockRecorder) EthGetTransactionCount(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionCount", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionCount), arg0, arg1, arg2) +} + +// EthGetTransactionReceipt mocks base method. +func (m *MockFullNode) EthGetTransactionReceipt(arg0 context.Context, arg1 api.EthHash) (api.EthTxReceipt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthGetTransactionReceipt", arg0, arg1) + ret0, _ := ret[0].(api.EthTxReceipt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthGetTransactionReceipt indicates an expected call of EthGetTransactionReceipt. +func (mr *MockFullNodeMockRecorder) EthGetTransactionReceipt(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthGetTransactionReceipt", reflect.TypeOf((*MockFullNode)(nil).EthGetTransactionReceipt), arg0, arg1) +} + +// EthMaxPriorityFeePerGas mocks base method. +func (m *MockFullNode) EthMaxPriorityFeePerGas(arg0 context.Context) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthMaxPriorityFeePerGas", arg0) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthMaxPriorityFeePerGas indicates an expected call of EthMaxPriorityFeePerGas. +func (mr *MockFullNodeMockRecorder) EthMaxPriorityFeePerGas(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthMaxPriorityFeePerGas", reflect.TypeOf((*MockFullNode)(nil).EthMaxPriorityFeePerGas), arg0) +} + +// EthProtocolVersion mocks base method. +func (m *MockFullNode) EthProtocolVersion(arg0 context.Context) (api.EthInt, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EthProtocolVersion", arg0) + ret0, _ := ret[0].(api.EthInt) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// EthProtocolVersion indicates an expected call of EthProtocolVersion. +func (mr *MockFullNodeMockRecorder) EthProtocolVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EthProtocolVersion", reflect.TypeOf((*MockFullNode)(nil).EthProtocolVersion), arg0) +} + // 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() @@ -1843,6 +2113,21 @@ func (mr *MockFullNodeMockRecorder) NetLimit(arg0, arg1 interface{}) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetLimit", reflect.TypeOf((*MockFullNode)(nil).NetLimit), arg0, arg1) } +// NetListening mocks base method. +func (m *MockFullNode) NetListening(arg0 context.Context) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetListening", arg0) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetListening indicates an expected call of NetListening. +func (mr *MockFullNodeMockRecorder) NetListening(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetListening", reflect.TypeOf((*MockFullNode)(nil).NetListening), arg0) +} + // NetPeerInfo mocks base method. func (m *MockFullNode) NetPeerInfo(arg0 context.Context, arg1 peer.ID) (*api.ExtendedPeerInfo, error) { m.ctrl.T.Helper() @@ -1975,6 +2260,21 @@ func (mr *MockFullNodeMockRecorder) NetStat(arg0, arg1 interface{}) *gomock.Call return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetStat", reflect.TypeOf((*MockFullNode)(nil).NetStat), arg0, arg1) } +// NetVersion mocks base method. +func (m *MockFullNode) NetVersion(arg0 context.Context) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NetVersion", arg0) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NetVersion indicates an expected call of NetVersion. +func (mr *MockFullNodeMockRecorder) NetVersion(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NetVersion", reflect.TypeOf((*MockFullNode)(nil).NetVersion), arg0) +} + // NodeStatus mocks base method. func (m *MockFullNode) NodeStatus(arg0 context.Context, arg1 bool) (api.NodeStatus, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index 54bc766b8..e585a1a5c 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -218,6 +218,42 @@ type FullNodeStruct struct { CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` + EthAccounts func(p0 context.Context) ([]EthAddress, error) `perm:"read"` + + EthBlockNumber func(p0 context.Context) (EthInt, error) `perm:"read"` + + EthChainId func(p0 context.Context) (EthInt, error) `perm:"read"` + + EthGasPrice func(p0 context.Context) (EthInt, error) `` + + EthGetBalance func(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) `perm:"read"` + + EthGetBlockByHash func(p0 context.Context, p1 EthHash, p2 bool) (EthBlock, error) `perm:"read"` + + EthGetBlockByNumber func(p0 context.Context, p1 EthInt, p2 bool) (EthBlock, error) `perm:"read"` + + EthGetBlockTransactionCountByHash func(p0 context.Context, p1 EthHash) (EthInt, error) `perm:"read"` + + EthGetBlockTransactionCountByNumber func(p0 context.Context, p1 EthInt) (EthInt, error) `perm:"read"` + + EthGetCode func(p0 context.Context, p1 EthAddress) (string, error) `perm:"read"` + + EthGetStorageAt func(p0 context.Context, p1 EthAddress, p2 EthInt, p3 string) (string, error) `perm:"read"` + + EthGetTransactionByBlockHashAndIndex func(p0 context.Context, p1 EthHash, p2 EthInt) (EthTx, error) `perm:"read"` + + EthGetTransactionByBlockNumberAndIndex func(p0 context.Context, p1 EthInt, p2 EthInt) (EthTx, error) `perm:"read"` + + EthGetTransactionByHash func(p0 context.Context, p1 EthHash) (EthTx, error) `perm:"read"` + + EthGetTransactionCount func(p0 context.Context, p1 EthAddress, p2 string) (EthInt, error) `perm:"read"` + + EthGetTransactionReceipt func(p0 context.Context, p1 EthHash) (EthTxReceipt, error) `perm:"read"` + + EthMaxPriorityFeePerGas func(p0 context.Context) (EthInt, error) `perm:"read"` + + EthProtocolVersion func(p0 context.Context) (EthInt, 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"` @@ -306,6 +342,10 @@ type FullNodeStruct struct { MsigSwapPropose func(p0 context.Context, p1 address.Address, p2 address.Address, p3 address.Address, p4 address.Address) (*MessagePrototype, error) `perm:"sign"` + NetListening func(p0 context.Context) (bool, error) `perm:"read"` + + NetVersion func(p0 context.Context) (string, error) `perm:"read"` + NodeStatus func(p0 context.Context, p1 bool) (NodeStatus, error) `perm:"read"` PaychAllocateLane func(p0 context.Context, p1 address.Address) (uint64, error) `perm:"sign"` @@ -1791,6 +1831,204 @@ func (s *FullNodeStub) CreateBackup(p0 context.Context, p1 string) error { return ErrNotSupported } +func (s *FullNodeStruct) EthAccounts(p0 context.Context) ([]EthAddress, error) { + if s.Internal.EthAccounts == nil { + return *new([]EthAddress), ErrNotSupported + } + return s.Internal.EthAccounts(p0) +} + +func (s *FullNodeStub) EthAccounts(p0 context.Context) ([]EthAddress, error) { + return *new([]EthAddress), ErrNotSupported +} + +func (s *FullNodeStruct) EthBlockNumber(p0 context.Context) (EthInt, error) { + if s.Internal.EthBlockNumber == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthBlockNumber(p0) +} + +func (s *FullNodeStub) EthBlockNumber(p0 context.Context) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthChainId(p0 context.Context) (EthInt, error) { + if s.Internal.EthChainId == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthChainId(p0) +} + +func (s *FullNodeStub) EthChainId(p0 context.Context) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGasPrice(p0 context.Context) (EthInt, error) { + if s.Internal.EthGasPrice == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthGasPrice(p0) +} + +func (s *FullNodeStub) EthGasPrice(p0 context.Context) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBalance(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) { + if s.Internal.EthGetBalance == nil { + return *new(EthBigInt), ErrNotSupported + } + return s.Internal.EthGetBalance(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBalance(p0 context.Context, p1 EthAddress, p2 string) (EthBigInt, error) { + return *new(EthBigInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByHash(p0 context.Context, p1 EthHash, p2 bool) (EthBlock, error) { + if s.Internal.EthGetBlockByHash == nil { + return *new(EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByHash(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByHash(p0 context.Context, p1 EthHash, p2 bool) (EthBlock, error) { + return *new(EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockByNumber(p0 context.Context, p1 EthInt, p2 bool) (EthBlock, error) { + if s.Internal.EthGetBlockByNumber == nil { + return *new(EthBlock), ErrNotSupported + } + return s.Internal.EthGetBlockByNumber(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetBlockByNumber(p0 context.Context, p1 EthInt, p2 bool) (EthBlock, error) { + return *new(EthBlock), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByHash(p0 context.Context, p1 EthHash) (EthInt, error) { + if s.Internal.EthGetBlockTransactionCountByHash == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByHash(p0 context.Context, p1 EthHash) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 EthInt) (EthInt, error) { + if s.Internal.EthGetBlockTransactionCountByNumber == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthGetBlockTransactionCountByNumber(p0, p1) +} + +func (s *FullNodeStub) EthGetBlockTransactionCountByNumber(p0 context.Context, p1 EthInt) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetCode(p0 context.Context, p1 EthAddress) (string, error) { + if s.Internal.EthGetCode == nil { + return "", ErrNotSupported + } + return s.Internal.EthGetCode(p0, p1) +} + +func (s *FullNodeStub) EthGetCode(p0 context.Context, p1 EthAddress) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) EthGetStorageAt(p0 context.Context, p1 EthAddress, p2 EthInt, p3 string) (string, error) { + if s.Internal.EthGetStorageAt == nil { + return "", ErrNotSupported + } + return s.Internal.EthGetStorageAt(p0, p1, p2, p3) +} + +func (s *FullNodeStub) EthGetStorageAt(p0 context.Context, p1 EthAddress, p2 EthInt, p3 string) (string, error) { + return "", ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 EthHash, p2 EthInt) (EthTx, error) { + if s.Internal.EthGetTransactionByBlockHashAndIndex == nil { + return *new(EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockHashAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockHashAndIndex(p0 context.Context, p1 EthHash, p2 EthInt) (EthTx, error) { + return *new(EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 EthInt, p2 EthInt) (EthTx, error) { + if s.Internal.EthGetTransactionByBlockNumberAndIndex == nil { + return *new(EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByBlockNumberAndIndex(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionByBlockNumberAndIndex(p0 context.Context, p1 EthInt, p2 EthInt) (EthTx, error) { + return *new(EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionByHash(p0 context.Context, p1 EthHash) (EthTx, error) { + if s.Internal.EthGetTransactionByHash == nil { + return *new(EthTx), ErrNotSupported + } + return s.Internal.EthGetTransactionByHash(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionByHash(p0 context.Context, p1 EthHash) (EthTx, error) { + return *new(EthTx), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionCount(p0 context.Context, p1 EthAddress, p2 string) (EthInt, error) { + if s.Internal.EthGetTransactionCount == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthGetTransactionCount(p0, p1, p2) +} + +func (s *FullNodeStub) EthGetTransactionCount(p0 context.Context, p1 EthAddress, p2 string) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthGetTransactionReceipt(p0 context.Context, p1 EthHash) (EthTxReceipt, error) { + if s.Internal.EthGetTransactionReceipt == nil { + return *new(EthTxReceipt), ErrNotSupported + } + return s.Internal.EthGetTransactionReceipt(p0, p1) +} + +func (s *FullNodeStub) EthGetTransactionReceipt(p0 context.Context, p1 EthHash) (EthTxReceipt, error) { + return *new(EthTxReceipt), ErrNotSupported +} + +func (s *FullNodeStruct) EthMaxPriorityFeePerGas(p0 context.Context) (EthInt, error) { + if s.Internal.EthMaxPriorityFeePerGas == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthMaxPriorityFeePerGas(p0) +} + +func (s *FullNodeStub) EthMaxPriorityFeePerGas(p0 context.Context) (EthInt, error) { + return *new(EthInt), ErrNotSupported +} + +func (s *FullNodeStruct) EthProtocolVersion(p0 context.Context) (EthInt, error) { + if s.Internal.EthProtocolVersion == nil { + return *new(EthInt), ErrNotSupported + } + return s.Internal.EthProtocolVersion(p0) +} + +func (s *FullNodeStub) EthProtocolVersion(p0 context.Context) (EthInt, error) { + return *new(EthInt), 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 @@ -2275,6 +2513,28 @@ func (s *FullNodeStub) MsigSwapPropose(p0 context.Context, p1 address.Address, p return nil, ErrNotSupported } +func (s *FullNodeStruct) NetListening(p0 context.Context) (bool, error) { + if s.Internal.NetListening == nil { + return false, ErrNotSupported + } + return s.Internal.NetListening(p0) +} + +func (s *FullNodeStub) NetListening(p0 context.Context) (bool, error) { + return false, ErrNotSupported +} + +func (s *FullNodeStruct) NetVersion(p0 context.Context) (string, error) { + if s.Internal.NetVersion == nil { + return "", ErrNotSupported + } + return s.Internal.NetVersion(p0) +} + +func (s *FullNodeStub) NetVersion(p0 context.Context) (string, error) { + return "", ErrNotSupported +} + func (s *FullNodeStruct) NodeStatus(p0 context.Context, p1 bool) (NodeStatus, error) { if s.Internal.NodeStatus == nil { return *new(NodeStatus), ErrNotSupported diff --git a/chain/store/store.go b/chain/store/store.go index 6313492a7..e04b17322 100644 --- a/chain/store/store.go +++ b/chain/store/store.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "context" "encoding/json" "errors" @@ -649,6 +650,12 @@ func (cs *ChainStore) takeHeaviestTipSet(ctx context.Context, ts *types.TipSet) return nil } + tskBlk, err := ts.Key().ToStorageBlock() + if err != nil { + log.Errorf("failed to create a block from tsk: %s", ts.Key()) + } + _ = cs.chainLocalBlockstore.Put(ctx, tskBlk) + return nil } @@ -1165,6 +1172,24 @@ func (cs *ChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, t return cs.LoadTipSet(ctx, lbts.Parents()) } +func (cs *ChainStore) GetTipSetByCid(ctx context.Context, c cid.Cid) (*types.TipSet, error) { + blk, err := cs.chainBlockstore.Get(ctx, c) + if err != nil { + return nil, xerrors.Errorf("cannot find tipset with cid %s: %w", c, err) + } + + tsk := new(types.TipSetKey) + if err := tsk.UnmarshalCBOR(bytes.NewReader(blk.RawData())); err != nil { + return nil, xerrors.Errorf("cannot unmarshal block into tipset key: %w", err) + } + + ts, err := cs.GetTipSetFromKey(ctx, *tsk) + if err != nil { + return nil, xerrors.Errorf("cannot get tipset from key: %w", err) + } + return ts, nil +} + func (cs *ChainStore) Weight(ctx context.Context, hts *types.TipSet) (types.BigInt, error) { // todo remove return cs.weight(ctx, cs.StateBlockstore(), hts) } diff --git a/chain/types/tipset_key.go b/chain/types/tipset_key.go index 59514a792..9dd736e73 100644 --- a/chain/types/tipset_key.go +++ b/chain/types/tipset_key.go @@ -7,6 +7,7 @@ import ( "io" "strings" + block "github.com/ipfs/go-block-format" "github.com/ipfs/go-cid" typegen "github.com/whyrusleeping/cbor-gen" @@ -98,6 +99,20 @@ func (k *TipSetKey) UnmarshalJSON(b []byte) error { return nil } +func (k TipSetKey) ToStorageBlock() (block.Block, error) { + buf := new(bytes.Buffer) + if err := k.MarshalCBOR(buf); err != nil { + log.Errorf("failed to marshal ts key as CBOR: %s", k) + } + + cid, err := abi.CidBuilder.Sum(buf.Bytes()) + if err != nil { + return nil, err + } + + return block.NewBlockWithCid(buf.Bytes(), cid) +} + func (k TipSetKey) MarshalCBOR(writer io.Writer) error { if err := typegen.WriteMajorTypeHeader(writer, typegen.MajByteString, uint64(len(k.Bytes()))); err != nil { return err diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index d2dc7e1f3..c0f52db79 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -65,6 +65,25 @@ * [ClientStatelessDeal](#ClientStatelessDeal) * [Create](#Create) * [CreateBackup](#CreateBackup) +* [Eth](#Eth) + * [EthAccounts](#EthAccounts) + * [EthBlockNumber](#EthBlockNumber) + * [EthChainId](#EthChainId) + * [EthGasPrice](#EthGasPrice) + * [EthGetBalance](#EthGetBalance) + * [EthGetBlockByHash](#EthGetBlockByHash) + * [EthGetBlockByNumber](#EthGetBlockByNumber) + * [EthGetBlockTransactionCountByHash](#EthGetBlockTransactionCountByHash) + * [EthGetBlockTransactionCountByNumber](#EthGetBlockTransactionCountByNumber) + * [EthGetCode](#EthGetCode) + * [EthGetStorageAt](#EthGetStorageAt) + * [EthGetTransactionByBlockHashAndIndex](#EthGetTransactionByBlockHashAndIndex) + * [EthGetTransactionByBlockNumberAndIndex](#EthGetTransactionByBlockNumberAndIndex) + * [EthGetTransactionByHash](#EthGetTransactionByHash) + * [EthGetTransactionCount](#EthGetTransactionCount) + * [EthGetTransactionReceipt](#EthGetTransactionReceipt) + * [EthMaxPriorityFeePerGas](#EthMaxPriorityFeePerGas) + * [EthProtocolVersion](#EthProtocolVersion) * [Gas](#Gas) * [GasEstimateFeeCap](#GasEstimateFeeCap) * [GasEstimateGasLimit](#GasEstimateGasLimit) @@ -135,6 +154,7 @@ * [NetDisconnect](#NetDisconnect) * [NetFindPeer](#NetFindPeer) * [NetLimit](#NetLimit) + * [NetListening](#NetListening) * [NetPeerInfo](#NetPeerInfo) * [NetPeers](#NetPeers) * [NetPing](#NetPing) @@ -144,6 +164,7 @@ * [NetPubsubScores](#NetPubsubScores) * [NetSetLimit](#NetSetLimit) * [NetStat](#NetStat) + * [NetVersion](#NetVersion) * [Node](#Node) * [NodeStatus](#NodeStatus) * [Paych](#Paych) @@ -2122,6 +2143,405 @@ Inputs: Response: `{}` +## Eth +These methods are used for Ethereum-compatible JSON-RPC calls + +EthAccounts will always return [] since we don't expect Lotus to manage private keys + + +### EthAccounts +There are not yet any comments for this method. + +Perms: read + +Inputs: `null` + +Response: +```json +[ + "0x0707070707070707070707070707070707070707" +] +``` + +### EthBlockNumber +EthBlockNumber returns the height of the latest (heaviest) TipSet + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthChainId + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthGasPrice + + +Perms: + +Inputs: `null` + +Response: `"0x5"` + +### EthGetBalance + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707", + "string value" +] +``` + +Response: `"0x0"` + +### EthGetBlockByHash + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707070707070707070707070707", + true +] +``` + +Response: +```json +{ + "parentHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "sha3Uncles": "0x0707070707070707070707070707070707070707070707070707070707070707", + "miner": "0x0707070707070707070707070707070707070707", + "stateRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "transactionsRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "receiptsRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "difficulty": "0x5", + "number": "0x5", + "gasLimit": "0x5", + "gasUsed": "0x5", + "timestamp": "0x5", + "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" + } +} +``` + +### EthGetBlockByNumber + + +Perms: read + +Inputs: +```json +[ + "0x5", + true +] +``` + +Response: +```json +{ + "parentHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "sha3Uncles": "0x0707070707070707070707070707070707070707070707070707070707070707", + "miner": "0x0707070707070707070707070707070707070707", + "stateRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "transactionsRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "receiptsRoot": "0x0707070707070707070707070707070707070707070707070707070707070707", + "difficulty": "0x5", + "number": "0x5", + "gasLimit": "0x5", + "gasUsed": "0x5", + "timestamp": "0x5", + "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" + } +} +``` + +### EthGetBlockTransactionCountByHash +EthGetBlockTransactionCountByHash returns the number of messages in the TipSet + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707070707070707070707070707" +] +``` + +Response: `"0x5"` + +### EthGetBlockTransactionCountByNumber +EthGetBlockTransactionCountByNumber returns the number of messages in the TipSet + + +Perms: read + +Inputs: +```json +[ + "0x5" +] +``` + +Response: `"0x5"` + +### EthGetCode + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707" +] +``` + +Response: `"string value"` + +### EthGetStorageAt + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707", + "0x5", + "string value" +] +``` + +Response: `"string value"` + +### EthGetTransactionByBlockHashAndIndex + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707070707070707070707070707", + "0x5" +] +``` + +Response: +```json +{ + "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" +} +``` + +### EthGetTransactionByBlockNumberAndIndex + + +Perms: read + +Inputs: +```json +[ + "0x5", + "0x5" +] +``` + +Response: +```json +{ + "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" +} +``` + +### EthGetTransactionByHash + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707070707070707070707070707" +] +``` + +Response: +```json +{ + "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" +} +``` + +### EthGetTransactionCount + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707", + "string value" +] +``` + +Response: `"0x5"` + +### EthGetTransactionReceipt + + +Perms: read + +Inputs: +```json +[ + "0x0707070707070707070707070707070707070707070707070707070707070707" +] +``` + +Response: +```json +{ + "transactionHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "transacionIndex": "0x5", + "blockHash": "0x0707070707070707070707070707070707070707070707070707070707070707", + "blockNumber": "0x0707070707070707070707070707070707070707070707070707070707070707", + "from": "0x0707070707070707070707070707070707070707", + "to": "0x0707070707070707070707070707070707070707", + "root": "0x0707070707070707070707070707070707070707070707070707070707070707", + "status": "0x5", + "contractAddress": "0x5cbeecf99d3fdb3f25e309cc264f240bb0664031", + "cumulativeGasUsed": "0x0", + "gasUsed": "0x0", + "effectiveGasPrice": "0x0" +} +``` + +### EthMaxPriorityFeePerGas + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + +### EthProtocolVersion + + +Perms: read + +Inputs: `null` + +Response: `"0x5"` + ## Gas @@ -4245,6 +4665,15 @@ Response: } ``` +### NetListening + + +Perms: read + +Inputs: `null` + +Response: `true` + ### NetPeerInfo @@ -4478,6 +4907,15 @@ Response: } ``` +### NetVersion + + +Perms: read + +Inputs: `null` + +Response: `"string value"` + ## Node These methods are general node management and status commands diff --git a/node/builder_chain.go b/node/builder_chain.go index 7a96e163c..2fe55b235 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -153,6 +153,7 @@ var ChainNode = Options( Override(new(messagepool.Provider), messagepool.NewProvider), Override(new(messagesigner.MpoolNonceAPI), From(new(*messagepool.MessagePool))), Override(new(full.ChainModuleAPI), From(new(full.ChainModule))), + Override(new(full.EthModuleAPI), From(new(full.EthModule))), Override(new(full.GasModuleAPI), From(new(full.GasModule))), Override(new(full.MpoolModuleAPI), From(new(full.MpoolModule))), Override(new(full.StateModuleAPI), From(new(full.StateModule))), diff --git a/node/impl/full.go b/node/impl/full.go index e1e7ac7a0..bc555c8c2 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -34,6 +34,7 @@ type FullNodeAPI struct { full.MsigAPI full.WalletAPI full.SyncAPI + full.EthAPI DS dtypes.MetadataDS NetworkName dtypes.NetworkName diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go new file mode 100644 index 000000000..5fe3eaa8b --- /dev/null +++ b/node/impl/full/eth.go @@ -0,0 +1,197 @@ +package full + +import ( + "context" + "strconv" + + "go.uber.org/fx" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" +) + +type EthModuleAPI interface { + EthBlockNumber(ctx context.Context) (api.EthInt, error) + EthAccounts(ctx context.Context) ([]api.EthAddress, error) + EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum api.EthInt) (api.EthInt, error) + EthGetBlockTransactionCountByHash(ctx context.Context, blkHash api.EthHash) (api.EthInt, error) + EthGetBlockByHash(ctx context.Context, blkHash api.EthHash, fullTxInfo bool) (api.EthBlock, error) + 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) + 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) + EthGetStorageAt(ctx context.Context, address api.EthAddress, position api.EthInt, blkParam string) (string, error) + EthGetBalance(ctx context.Context, address api.EthAddress, blkParam string) (api.EthBigInt, error) + EthChainId(ctx context.Context) (api.EthInt, error) + 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) + // EthSendRawTransaction(ctx context.Context, tx api.EthTx) (api.EthHash, error) +} + +var _ EthModuleAPI = *new(api.FullNode) + +// EthModule provides a default implementation of EthModuleAPI. +// It can be swapped out with another implementation through Dependency +// Injection (for example with a thin RPC client). +type EthModule struct { + fx.In + + Chain *store.ChainStore + StateAPI +} + +var _ EthModuleAPI = (*EthModule)(nil) + +type EthAPI struct { + fx.In + + Chain *store.ChainStore + + EthModuleAPI +} + +func (a *EthModule) EthBlockNumber(context.Context) (api.EthInt, error) { + height := a.Chain.GetHeaviestTipSet().Height() + return api.EthInt(height), nil +} + +func (a *EthModule) EthAccounts(context.Context) ([]api.EthAddress, error) { + // The lotus node is not expected to hold manage accounts, so we'll always return an empty array + return []api.EthAddress{}, nil +} + +func (a *EthModule) countTipsetMsgs(ctx context.Context, ts *types.TipSet) (int, error) { + blkMsgs, err := a.Chain.BlockMsgsForTipset(ctx, ts) + if err != nil { + return 0, xerrors.Errorf("error loading messages for tipset: %v: %w", ts, err) + } + + count := 0 + for _, blkMsg := range blkMsgs { + // TODO: may need to run canonical ordering and deduplication here + count += len(blkMsg.BlsMessages) + len(blkMsg.SecpkMessages) + } + return count, nil +} + +func (a *EthModule) EthGetBlockTransactionCountByNumber(ctx context.Context, blkNum api.EthInt) (api.EthInt, error) { + ts, err := a.Chain.GetTipsetByHeight(ctx, abi.ChainEpoch(blkNum), nil, false) + if err != nil { + return api.EthInt(0), xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + + count, err := a.countTipsetMsgs(ctx, ts) + return api.EthInt(count), err +} + +func (a *EthModule) EthGetBlockTransactionCountByHash(ctx context.Context, blkHash api.EthHash) (api.EthInt, error) { + ts, err := a.Chain.GetTipSetByCid(ctx, blkHash.ToCid()) + if err != nil { + return api.EthInt(0), xerrors.Errorf("error loading tipset %s: %w", ts, err) + } + count, err := a.countTipsetMsgs(ctx, ts) + return api.EthInt(count), err +} + +func (a *EthModule) EthGetBlockByHash(ctx context.Context, blkHash api.EthHash, fullTxInfo bool) (api.EthBlock, error) { + return api.EthBlock{}, nil +} + +func (a *EthModule) EthGetBlockByNumber(ctx context.Context, blkNum api.EthInt, fullTxInfo bool) (api.EthBlock, error) { + return api.EthBlock{}, nil +} + +func (a *EthModule) EthGetTransactionByHash(ctx context.Context, txHash api.EthHash) (api.EthTx, error) { + return api.EthTx{}, nil +} + +func (a *EthModule) EthGetTransactionCount(ctx context.Context, sender api.EthAddress, blkParam string) (api.EthInt, error) { + return api.EthInt(0), nil +} + +func (a *EthModule) EthGetTransactionReceipt(ctx context.Context, blkHash api.EthHash) (api.EthTxReceipt, error) { + return api.EthTxReceipt{}, nil +} + +func (a *EthModule) EthGetTransactionByBlockHashAndIndex(ctx context.Context, blkHash api.EthHash, txIndex api.EthInt) (api.EthTx, error) { + return api.EthTx{}, nil +} + +func (a *EthModule) EthGetTransactionByBlockNumberAndIndex(ctx context.Context, blkNum api.EthInt, txIndex api.EthInt) (api.EthTx, error) { + return api.EthTx{}, nil +} + +// EthGetCode returns string value of the compiled bytecode +func (a *EthModule) EthGetCode(ctx context.Context, address api.EthAddress) (string, error) { + return "", nil +} + +func (a *EthModule) EthGetStorageAt(ctx context.Context, address api.EthAddress, position api.EthInt, blkParam string) (string, error) { + return "", nil +} + +func (a *EthModule) EthGetBalance(ctx context.Context, address api.EthAddress, blkParam string) (api.EthBigInt, error) { + filAddr, err := address.ToFilecoinAddress() + if err != nil { + return api.EthBigInt{}, err + } + + actor, err := a.StateGetActor(ctx, filAddr, types.EmptyTSK) + if err != nil { + return api.EthBigInt{}, err + } + + return api.EthBigInt{Int: actor.Balance.Int}, nil +} + +func (a *EthModule) EthChainId(ctx context.Context) (api.EthInt, error) { + return api.EthInt(0), nil +} + +func (a *EthModule) NetVersion(ctx context.Context) (string, error) { + // Note that networkId is not encoded in hex + nv, err := a.StateNetworkVersion(ctx, types.EmptyTSK) + if err != nil { + return "", err + } + return strconv.FormatUint(uint64(nv), 10), nil +} + +func (a *EthModule) NetListening(ctx context.Context) (bool, error) { + return true, nil +} + +func (a *EthModule) EthProtocolVersion(ctx context.Context) (api.EthInt, error) { + return api.EthInt(0), nil +} + +func (a *EthModule) EthMaxPriorityFeePerGas(ctx context.Context) (api.EthInt, error) { + return api.EthInt(0), nil +} + +func (a *EthModule) EthGasPrice(ctx context.Context) (api.EthInt, error) { + return api.EthInt(0), nil +} + +// func (a *EthModule) EthSendRawTransaction(ctx context.Context tx api.EthTx) (api.EthHash, 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 +// } +// diff --git a/node/rpc.go b/node/rpc.go index 2c85c71be..8a2fe079c 100644 --- a/node/rpc.go +++ b/node/rpc.go @@ -73,6 +73,34 @@ func FullNodeHandler(a v1api.FullNode, permissioned bool, opts ...jsonrpc.Server rpcServer.Register("Filecoin", hnd) rpcServer.AliasMethod("rpc.discover", "Filecoin.Discover") + // TODO: use reflect to automatically register all the eth aliases + rpcServer.AliasMethod("eth_accounts", "Filecoin.EthAccounts") + rpcServer.AliasMethod("eth_blockNumber", "Filecoin.EthBlockNumber") + rpcServer.AliasMethod("eth_getBlockTransactionCountByNumber", "Filecoin.EthGetBlockTransactionCountByNumber") + rpcServer.AliasMethod("eth_getBlockTransactionCountByHash", "Filecoin.EthGetBlockTransactionCountByHash") + + rpcServer.AliasMethod("eth_getBlockByHash", "Filecoin.EthGetBlockByHash") + rpcServer.AliasMethod("eth_getBlockByNumber", "Filecoin.EthGetBlockByNumber") + rpcServer.AliasMethod("eth_getTransactionByHash", "Filecoin.EthGetTransactionByHash") + rpcServer.AliasMethod("eth_getTransactionCount", "Filecoin.EthGetTransactionCount") + rpcServer.AliasMethod("eth_getTransactionReceipt", "Filecoin.EthGetTransactionReceipt") + rpcServer.AliasMethod("eth_getTransactionByBlockHashAndIndex", "Filecoin.EthGetTransactionByBlockHashAndIndex") + rpcServer.AliasMethod("eth_getTransactionByBlockNumberAndIndex", "Filecoin.EthGetTransactionByBlockNumberAndIndex") + + rpcServer.AliasMethod("eth_getCode", "Filecoin.EthGetCode") + rpcServer.AliasMethod("eth_getStorageAt", "Filecoin.EthGetStorageAt") + rpcServer.AliasMethod("eth_getBalance", "Filecoin.EthGetBalance") + rpcServer.AliasMethod("eth_chainId", "Filecoin.EthChainId") + rpcServer.AliasMethod("eth_protocolVersion", "Filecoin.EthProtocolVersion") + 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("net_version", "Filecoin.NetVersion") + rpcServer.AliasMethod("net_listening", "Filecoin.NetListening") + var handler http.Handler = rpcServer if permissioned { handler = &auth.Handler{Verify: a.AuthVerify, Next: rpcServer.ServeHTTP}