[WIP] Ethereum RPC compatibility (support for MetaMask, web3.js, ethers.js etc) (#3495)

* Initial support for read only eth_ API:s

* Support eth_getBalance

* Initial implementation of SendRawTransaction

* Remove unused import

* Remove unused code

* Better order of switch statements

* Make tx signing API:s backwards compatible + fix tests

* Refactor tx signing API

* Revert to main implementation

* Revert to main implementation

* Add comparisons for params.EthMainnetChainID - stops panics

* Refactor chainId verification

* refactor SendRawTransaction

* FIx chainId mismatches - use ChainConfig().EthCompatibleChainID everywhere

* Refactor chainID comparison

Co-authored-by: Rongjian Lan <rongjian.lan@gmail.com>
pull/3498/head
Sebastian Johnsson 4 years ago committed by GitHub
parent 33aaf2db8a
commit dcf65af9c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 13
      core/types/eth_transaction.go
  2. 7
      core/types/transaction_signing.go
  3. 16
      internal/params/config.go
  4. 31
      rpc/blockchain.go
  5. 4
      rpc/harmony.go
  6. 4
      rpc/net.go
  7. 44
      rpc/pool.go
  8. 15
      rpc/rpc.go
  9. 26
      rpc/transaction.go
  10. 8
      rpc/v1/legacy.go
  11. 8
      rpc/v2/legacy.go

@ -21,10 +21,10 @@ import (
"math/big"
"sync/atomic"
"github.com/harmony-one/harmony/internal/params"
"github.com/ethereum/go-ethereum/common/hexutil"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/crypto/hash"
"github.com/ethereum/go-ethereum/common"
@ -151,12 +151,17 @@ func (tx *EthTransaction) Data() []byte {
// ShardID returns which shard id this transaction was signed for (if at all)
func (tx *EthTransaction) ShardID() uint32 {
return uint32(tx.ChainID().Uint64() - params.EthMainnetChainID.Uint64())
return tx.shardID()
}
// ToShardID returns the destination shard id this transaction is going to
func (tx *EthTransaction) ToShardID() uint32 {
return uint32(tx.ChainID().Uint64() - params.EthMainnetChainID.Uint64())
return tx.shardID()
}
func (tx *EthTransaction) shardID() uint32 {
ethChainID := nodeconfig.GetDefaultConfig().GetNetworkType().ChainConfig().EthCompatibleChainID
return uint32(tx.ChainID().Uint64() - ethChainID.Uint64())
}
// ChainID returns which chain id this transaction was signed for (if at all)

@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/harmony-one/harmony/crypto/hash"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/params"
)
@ -122,7 +123,8 @@ func NewEIP155Signer(chainID *big.Int) EIP155Signer {
// Equal checks if the given EIP155Signer is equal to another Signer.
func (s EIP155Signer) Equal(s2 Signer) bool {
eip155, ok := s2.(EIP155Signer)
return ok && eip155.chainID.Cmp(s.chainID) == 0
ethChainID := nodeconfig.GetDefaultConfig().GetNetworkType().ChainConfig().EthCompatibleChainID
return ok && (eip155.chainID.Cmp(ethChainID) == 0 || eip155.chainID.Cmp(s.chainID) == 0)
}
var big8 = big.NewInt(8)
@ -133,7 +135,8 @@ func (s EIP155Signer) Sender(tx InternalTransaction) (common.Address, error) {
return HomesteadSigner{}.Sender(tx)
}
if tx.ChainID().Cmp(s.chainID) != 0 {
ethChainID := nodeconfig.GetDefaultConfig().GetNetworkType().ChainConfig().EthCompatibleChainID
if tx.ChainID().Cmp(ethChainID) != 0 && tx.ChainID().Cmp(s.chainID) != 0 {
return common.Address{}, ErrInvalidChainID
}
V := new(big.Int).Sub(tx.V(), s.chainIDMul)

@ -362,14 +362,14 @@ func (c *ChainConfig) IsReceiptLog(epoch *big.Int) bool {
// UpdateEthChainIDByShard update the ethChainID based on shard ID.
func UpdateEthChainIDByShard(shardID uint32) {
once.Do(func() {
MainnetChainConfig.EthCompatibleChainID.Add(MainnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
TestnetChainConfig.EthCompatibleChainID.Add(TestnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
PangaeaChainConfig.EthCompatibleChainID.Add(PangaeaChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
PartnerChainConfig.EthCompatibleChainID.Add(PartnerChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
StressnetChainConfig.EthCompatibleChainID.Add(StressnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
LocalnetChainConfig.EthCompatibleChainID.Add(LocalnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
AllProtocolChanges.EthCompatibleChainID.Add(AllProtocolChanges.EthCompatibleChainID, big.NewInt(int64(shardID)))
TestChainConfig.EthCompatibleChainID.Add(TestChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
MainnetChainConfig.EthCompatibleChainID = big.NewInt(0).Add(MainnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
TestnetChainConfig.EthCompatibleChainID = big.NewInt(0).Add(TestnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
PangaeaChainConfig.EthCompatibleChainID = big.NewInt(0).Add(PangaeaChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
PartnerChainConfig.EthCompatibleChainID = big.NewInt(0).Add(PartnerChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
StressnetChainConfig.EthCompatibleChainID = big.NewInt(0).Add(StressnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
LocalnetChainConfig.EthCompatibleChainID = big.NewInt(0).Add(LocalnetChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
AllProtocolChanges.EthCompatibleChainID = big.NewInt(0).Add(AllProtocolChanges.EthCompatibleChainID, big.NewInt(int64(shardID)))
TestChainConfig.EthCompatibleChainID = big.NewInt(0).Add(TestChainConfig.EthCompatibleChainID, big.NewInt(int64(shardID)))
})
}

@ -12,6 +12,7 @@ import (
"github.com/harmony-one/harmony/consensus/reward"
"github.com/harmony-one/harmony/hmy"
internal_common "github.com/harmony-one/harmony/internal/common"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/utils"
"github.com/harmony-one/harmony/numeric"
rpc_common "github.com/harmony-one/harmony/rpc/common"
@ -41,10 +42,26 @@ func NewPublicBlockchainAPI(hmy *hmy.Harmony, version Version) rpc.API {
}
}
// ChainId returns the chain id of the chain - required by MetaMask
func (s *PublicBlockchainService) ChainId(ctx context.Context) (interface{}, error) {
// Format return base on version
switch s.version {
case V1:
return hexutil.Uint64(s.hmy.ChainID), nil
case V2:
return s.hmy.ChainID, nil
case Eth:
ethChainID := nodeconfig.GetDefaultConfig().GetNetworkType().ChainConfig().EthCompatibleChainID
return hexutil.Uint64(ethChainID.Uint64()), nil
default:
return nil, ErrUnknownRPCVersion
}
}
// getBlockOptions is a helper to get block args given an interface option from RPC params.
func (s *PublicBlockchainService) getBlockOptions(opts interface{}) (*rpc_common.BlockArgs, error) {
switch s.version {
case V1:
case V1, Eth:
fullTx, ok := opts.(bool)
if !ok {
return nil, fmt.Errorf("invalid type for block arguments")
@ -87,7 +104,7 @@ func (s *PublicBlockchainService) BlockNumber(ctx context.Context) (interface{},
// Format return base on version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint64(header.Number().Uint64()), nil
case V2:
return header.Number().Uint64(), nil
@ -133,7 +150,7 @@ func (s *PublicBlockchainService) GetBlockByNumber(
leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch())
var rpcBlock interface{}
switch s.version {
case V1:
case V1, Eth:
rpcBlock, err = v1.NewBlock(blk, blockArgs, leader)
case V2:
rpcBlock, err = v2.NewBlock(blk, blockArgs, leader)
@ -190,7 +207,7 @@ func (s *PublicBlockchainService) GetBlockByHash(
leader := s.hmy.GetLeaderAddress(blk.Header().Coinbase(), blk.Header().Epoch())
var rpcBlock interface{}
switch s.version {
case V1:
case V1, Eth:
rpcBlock, err = v1.NewBlock(blk, blockArgs, leader)
case V2:
rpcBlock, err = v2.NewBlock(blk, blockArgs, leader)
@ -385,7 +402,7 @@ func (s *PublicBlockchainService) GetSignedBlocks(
// Format the response according to the version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint64(totalSigned), nil
case V2:
return totalSigned, nil
@ -405,7 +422,7 @@ func (s *PublicBlockchainService) GetEpoch(ctx context.Context) (interface{}, er
// Format the response according to the version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint64(epoch), nil
case V2:
return epoch, nil
@ -463,7 +480,7 @@ func (s *PublicBlockchainService) GetBalanceByBlockNumber(
// Format return base on version
switch s.version {
case V1:
case V1, Eth:
return (*hexutil.Big)(balance), nil
case V2:
return balance, nil

@ -33,7 +33,7 @@ func (s *PublicHarmonyService) ProtocolVersion(
) (interface{}, error) {
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(s.hmy.ProtocolVersion()), nil
case V2:
return s.hmy.ProtocolVersion(), nil
@ -62,7 +62,7 @@ func (s *PublicHarmonyService) GasPrice(ctx context.Context) (interface{}, error
// TODO(dm): add SuggestPrice API
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return (*hexutil.Big)(big.NewInt(1)), nil
case V2:
return 1, nil

@ -22,7 +22,7 @@ func NewPublicNetAPI(net p2p.Host, chainID uint64, version Version) rpc.API {
// manually set different namespace to preserve legacy behavior
var namespace string
switch version {
case V1:
case V1, Eth:
namespace = netV1Namespace
case V2:
namespace = netV2Namespace
@ -44,7 +44,7 @@ func NewPublicNetAPI(net p2p.Host, chainID uint64, version Version) rpc.API {
func (s *PublicNetService) PeerCount(ctx context.Context) (interface{}, error) {
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(s.net.GetPeerCount()), nil
case V2:
return s.net.GetPeerCount(), nil

@ -14,6 +14,7 @@ import (
"github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/hmy"
common2 "github.com/harmony-one/harmony/internal/common"
nodeconfig "github.com/harmony-one/harmony/internal/configs/node"
"github.com/harmony-one/harmony/internal/utils"
v1 "github.com/harmony-one/harmony/rpc/v1"
v2 "github.com/harmony-one/harmony/rpc/v2"
@ -48,16 +49,24 @@ func (s *PublicPoolService) SendRawTransaction(
return common.Hash{}, err
}
// Verify transaction type & chain
tx := new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
var tx *types.Transaction
if s.version == Eth {
ethTx := new(types.EthTransaction)
if err := rlp.DecodeBytes(encodedTx, ethTx); err != nil {
return common.Hash{}, err
}
tx = ethTx.ConvertToHmy()
} else {
tx = new(types.Transaction)
if err := rlp.DecodeBytes(encodedTx, tx); err != nil {
return common.Hash{}, err
}
}
c := s.hmy.ChainConfig().ChainID
if id := tx.ChainID(); id.Cmp(c) != 0 {
return common.Hash{}, errors.Wrapf(
ErrInvalidChainID, "blockchain chain id:%s, given %s", c.String(), id.String(),
)
// Verify chainID
if err := s.verifyChainID(tx); err != nil {
return common.Hash{}, err
}
// Submit transaction
@ -89,6 +98,19 @@ func (s *PublicPoolService) SendRawTransaction(
return tx.Hash(), nil
}
func (s *PublicPoolService) verifyChainID(tx *types.Transaction) error {
nodeChainID := s.hmy.ChainConfig().ChainID
ethChainID := nodeconfig.GetDefaultConfig().GetNetworkType().ChainConfig().EthCompatibleChainID
if tx.ChainID().Cmp(ethChainID) != 0 && tx.ChainID().Cmp(nodeChainID) != 0 {
return errors.Wrapf(
ErrInvalidChainID, "blockchain chain id:%s, given %s", nodeChainID.String(), tx.ChainID().String(),
)
}
return nil
}
// SendRawStakingTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicPoolService) SendRawStakingTransaction(
@ -156,7 +178,7 @@ func (s *PublicPoolService) PendingTransactions(
if plainTx, ok := pending[i].(*types.Transaction); ok {
var tx interface{}
switch s.version {
case V1:
case V1, Eth:
tx, err = v1.NewTransaction(plainTx, common.Hash{}, 0, 0, 0)
if err != nil {
utils.Logger().Debug().
@ -211,7 +233,7 @@ func (s *PublicPoolService) PendingStakingTransactions(
} else if stakingTx, ok := pending[i].(*staking.StakingTransaction); ok {
var tx interface{}
switch s.version {
case V1:
case V1, Eth:
tx, err = v1.NewStakingTransaction(stakingTx, common.Hash{}, 0, 0, 0)
if err != nil {
utils.Logger().Debug().

@ -18,6 +18,7 @@ import (
const (
V1 Version = iota
V2
Eth
Debug
)
@ -39,9 +40,9 @@ const (
var (
// HTTPModules ..
HTTPModules = []string{"hmy", "hmyv2", "debug", netV1Namespace, netV2Namespace, "explorer"}
HTTPModules = []string{"hmy", "hmyv2", "eth", "debug", netV1Namespace, netV2Namespace, "explorer"}
// WSModules ..
WSModules = []string{"hmy", "hmyv2", "debug", netV1Namespace, netV2Namespace, "web3"}
WSModules = []string{"hmy", "hmyv2", "eth", "debug", netV1Namespace, netV2Namespace, "web3"}
httpListener net.Listener
httpHandler *rpc.Server
@ -121,20 +122,26 @@ func getAPIs(hmy *hmy.Harmony, debugEnable bool) []rpc.API {
// Public methods
NewPublicHarmonyAPI(hmy, V1),
NewPublicHarmonyAPI(hmy, V2),
NewPublicHarmonyAPI(hmy, Eth),
NewPublicBlockchainAPI(hmy, V1),
NewPublicBlockchainAPI(hmy, V2),
NewPublicBlockchainAPI(hmy, Eth),
NewPublicContractAPI(hmy, V1),
NewPublicContractAPI(hmy, V2),
NewPublicContractAPI(hmy, Eth),
NewPublicTransactionAPI(hmy, V1),
NewPublicTransactionAPI(hmy, V2),
NewPublicTransactionAPI(hmy, Eth),
NewPublicPoolAPI(hmy, V1),
NewPublicPoolAPI(hmy, V2),
NewPublicPoolAPI(hmy, Eth),
NewPublicStakingAPI(hmy, V1),
NewPublicStakingAPI(hmy, V2),
NewPublicTracerAPI(hmy, Debug),
// Legacy methods (subject to removal)
v1.NewPublicLegacyAPI(hmy),
v2.NewPublicLegacyAPI(hmy),
v1.NewPublicLegacyAPI(hmy, "hmy"),
v1.NewPublicLegacyAPI(hmy, "eth"),
v2.NewPublicLegacyAPI(hmy, "hmyv2"),
}
privateAPIs := []rpc.API{

@ -92,7 +92,7 @@ func (s *PublicTransactionService) GetTransactionCount(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return (hexutil.Uint64)(nonce), nil
case V2:
return nonce, nil
@ -173,7 +173,7 @@ func (s *PublicTransactionService) GetTransactionByHash(
// Format the response according to the version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewTransaction(tx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
@ -213,7 +213,7 @@ func (s *PublicTransactionService) GetStakingTransactionByHash(
}
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewStakingTransaction(stx, blockHash, blockNumber, block.Time().Uint64(), index)
if err != nil {
return nil, err
@ -348,7 +348,7 @@ func (s *PublicTransactionService) GetBlockTransactionCountByNumber(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(len(block.Transactions())), nil
case V2:
return len(block.Transactions()), nil
@ -374,7 +374,7 @@ func (s *PublicTransactionService) GetBlockTransactionCountByHash(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(len(block.Transactions())), nil
case V2:
return len(block.Transactions()), nil
@ -402,7 +402,7 @@ func (s *PublicTransactionService) GetTransactionByBlockNumberAndIndex(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
@ -435,7 +435,7 @@ func (s *PublicTransactionService) GetTransactionByBlockHashAndIndex(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
@ -472,7 +472,7 @@ func (s *PublicTransactionService) GetBlockStakingTransactionCountByNumber(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(len(block.StakingTransactions())), nil
case V2:
return len(block.StakingTransactions()), nil
@ -498,7 +498,7 @@ func (s *PublicTransactionService) GetBlockStakingTransactionCountByHash(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
return hexutil.Uint(len(block.StakingTransactions())), nil
case V2:
return len(block.StakingTransactions()), nil
@ -526,7 +526,7 @@ func (s *PublicTransactionService) GetStakingTransactionByBlockNumberAndIndex(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
@ -559,7 +559,7 @@ func (s *PublicTransactionService) GetStakingTransactionByBlockHashAndIndex(
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewStakingTransactionFromBlockIndex(block, uint64(index))
if err != nil {
return nil, err
@ -608,7 +608,7 @@ func (s *PublicTransactionService) GetTransactionReceipt(
// Format response according to version
var RPCReceipt interface{}
switch s.version {
case V1:
case V1, Eth:
if tx == nil {
RPCReceipt, err = v1.NewReceipt(stx, blockHash, blockNumber, index, receipt)
} else {
@ -640,7 +640,7 @@ func (s *PublicTransactionService) GetCXReceiptByHash(
if cx, blockHash, blockNumber, _ := rawdb.ReadCXReceipt(s.hmy.ChainDb(), hash); cx != nil {
// Format response according to version
switch s.version {
case V1:
case V1, Eth:
tx, err := v1.NewCxReceipt(cx, blockHash, blockNumber)
if err != nil {
return nil, err

@ -16,9 +16,13 @@ type PublicLegacyService struct {
}
// NewPublicLegacyAPI creates a new API for the RPC interface
func NewPublicLegacyAPI(hmy *hmy.Harmony) rpc.API {
func NewPublicLegacyAPI(hmy *hmy.Harmony, namespace string) rpc.API {
if namespace == "" {
namespace = "hmy"
}
return rpc.API{
Namespace: "hmy",
Namespace: namespace,
Version: "1.0",
Service: &PublicLegacyService{hmy},
Public: true,

@ -16,9 +16,13 @@ type PublicLegacyService struct {
}
// NewPublicLegacyAPI creates a new API for the RPC interface
func NewPublicLegacyAPI(hmy *hmy.Harmony) rpc.API {
func NewPublicLegacyAPI(hmy *hmy.Harmony, namespace string) rpc.API {
if namespace == "" {
namespace = "hmyv2"
}
return rpc.API{
Namespace: "hmyv2",
Namespace: namespace,
Version: "1.0",
Service: &PublicLegacyService{hmy},
Public: true,

Loading…
Cancel
Save