chore_: implement eth service for use in integration tests (#5903)
This commit is contained in:
parent
c1dd9397f7
commit
e8bd4d5685
|
@ -39,6 +39,7 @@ import (
|
|||
"github.com/status-im/status-go/services/communitytokens"
|
||||
"github.com/status-im/status-go/services/connector"
|
||||
"github.com/status-im/status-go/services/ens"
|
||||
"github.com/status-im/status-go/services/eth"
|
||||
"github.com/status-im/status-go/services/gif"
|
||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||
"github.com/status-im/status-go/services/mailservers"
|
||||
|
@ -132,6 +133,7 @@ type StatusNode struct {
|
|||
pendingTracker *transactions.PendingTxTracker
|
||||
connectorSrvc *connector.Service
|
||||
appGeneralSrvc *appgeneral.Service
|
||||
ethSrvc *eth.Service
|
||||
|
||||
accountsFeed event.Feed
|
||||
walletFeed event.Feed
|
||||
|
|
|
@ -40,6 +40,7 @@ import (
|
|||
"github.com/status-im/status-go/services/communitytokens"
|
||||
"github.com/status-im/status-go/services/connector"
|
||||
"github.com/status-im/status-go/services/ens"
|
||||
"github.com/status-im/status-go/services/eth"
|
||||
"github.com/status-im/status-go/services/ext"
|
||||
"github.com/status-im/status-go/services/gif"
|
||||
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
||||
|
@ -174,6 +175,8 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
|
|||
}
|
||||
services = append(services, lns)
|
||||
|
||||
services = append(services, b.ethService())
|
||||
|
||||
b.peerSrvc.SetDiscoverer(b)
|
||||
|
||||
for i := range services {
|
||||
|
@ -617,6 +620,13 @@ func (b *StatusNode) peerService() *peer.Service {
|
|||
return b.peerSrvc
|
||||
}
|
||||
|
||||
func (b *StatusNode) ethService() *eth.Service {
|
||||
if b.ethSrvc == nil {
|
||||
b.ethSrvc = eth.NewService(b.rpcClient)
|
||||
}
|
||||
return b.ethSrvc
|
||||
}
|
||||
|
||||
func registerWakuMailServer(wakuService *waku.Waku, config *params.WakuConfig) (err error) {
|
||||
var mailServer mailserver.WakuMailServer
|
||||
wakuService.RegisterMailServer(&mailServer)
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
//go:build enable_private_api
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
geth_rpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
func privateAPIs(client *rpc.Client) (apis []geth_rpc.API) {
|
||||
return []geth_rpc.API{
|
||||
{
|
||||
Namespace: "ethclient",
|
||||
Version: "1.0",
|
||||
Service: NewPrivateAPI(client),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type PrivateAPI struct {
|
||||
client *rpc.Client
|
||||
}
|
||||
|
||||
func NewPrivateAPI(client *rpc.Client) *PrivateAPI {
|
||||
return &PrivateAPI{client: client}
|
||||
}
|
||||
|
||||
type blockResponse struct {
|
||||
Header *types.Header `json:"header"`
|
||||
Transactions types.Transactions `json:"transactions"`
|
||||
Withdrawals types.Withdrawals `json:"withdrawals"`
|
||||
}
|
||||
|
||||
func newBlockResponse(b *types.Block) *blockResponse {
|
||||
return &blockResponse{
|
||||
Header: b.Header(),
|
||||
Transactions: b.Transactions(),
|
||||
Withdrawals: b.Withdrawals(),
|
||||
}
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) BlockByHash(ctx context.Context, chainId uint64, hash common.Hash) (*blockResponse, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := client.BlockByHash(ctx, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBlockResponse(block), nil
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) BlockByNumber(ctx context.Context, chainId uint64, number *hexutil.Big) (*blockResponse, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
block, err := client.BlockByNumber(ctx, (*big.Int)(number))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return newBlockResponse(block), nil
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) HeaderByHash(ctx context.Context, chainId uint64, hash common.Hash) (*types.Header, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.HeaderByHash(ctx, hash)
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) HeaderByNumber(ctx context.Context, chainId uint64, number *hexutil.Big) (*types.Header, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.HeaderByNumber(ctx, (*big.Int)(number))
|
||||
}
|
||||
|
||||
type transactionByHashResponse struct {
|
||||
Tx *types.Transaction `json:"tx"`
|
||||
IsPending bool `json:"isPending"`
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) TransactionByHash(ctx context.Context, chainId uint64, txHash common.Hash) (*transactionByHashResponse, error) {
|
||||
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx, isPending, err := client.TransactionByHash(ctx, txHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := &transactionByHashResponse{
|
||||
Tx: tx,
|
||||
IsPending: isPending,
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) TransactionReceipt(ctx context.Context, chainId uint64, txHash common.Hash) (*types.Receipt, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client.TransactionReceipt(ctx, txHash)
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) SuggestGasPrice(ctx context.Context, chainId uint64) (*hexutil.Big, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret, err := client.SuggestGasPrice(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return (*hexutil.Big)(ret), nil
|
||||
}
|
||||
|
||||
func (pa *PrivateAPI) BlockNumber(ctx context.Context, chainId uint64) (uint64, error) {
|
||||
client, err := pa.client.EthClient(chainId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return client.BlockNumber(ctx)
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
//go:build !enable_private_api
|
||||
|
||||
package eth
|
||||
|
||||
import (
|
||||
geth_rpc "github.com/ethereum/go-ethereum/rpc"
|
||||
"github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
func privateAPIs(*rpc.Client) (apis []geth_rpc.API) {
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
geth_rpc "github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
rpc_client "github.com/status-im/status-go/rpc"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
rpcClient *rpc_client.Client
|
||||
}
|
||||
|
||||
func NewService(
|
||||
rpcClient *rpc_client.Client,
|
||||
) *Service {
|
||||
return &Service{
|
||||
rpcClient: rpcClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) APIs() []geth_rpc.API {
|
||||
return privateAPIs(s.rpcClient)
|
||||
}
|
||||
|
||||
func (s *Service) Protocols() []p2p.Protocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Stop() error {
|
||||
return nil
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
"HTTPHost": "0.0.0.0",
|
||||
"HTTPPort": 3333,
|
||||
"HTTPVirtualHosts": ["*", "status-go"],
|
||||
"APIModules": "eth,admin,wallet,accounts,waku,wakuext",
|
||||
"APIModules": "eth,admin,wallet,accounts,waku,wakuext,ethclient",
|
||||
"WalletConfig": {
|
||||
"Enabled": true
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@ services:
|
|||
context: ../
|
||||
dockerfile: _assets/build/Dockerfile
|
||||
args:
|
||||
build_tags: gowaku_no_rln
|
||||
build_tags: gowaku_no_rln,enable_private_api
|
||||
build_target: statusd
|
||||
build_flags: -cover
|
||||
entrypoint: [
|
||||
|
@ -37,7 +37,7 @@ services:
|
|||
context: ../
|
||||
dockerfile: _assets/build/Dockerfile
|
||||
args:
|
||||
build_tags: gowaku_no_rln
|
||||
build_tags: gowaku_no_rln,enable_private_api
|
||||
build_target: statusd
|
||||
build_flags: -cover
|
||||
entrypoint: [
|
||||
|
@ -72,7 +72,7 @@ services:
|
|||
dockerfile: Dockerfile.tests-rpc
|
||||
entrypoint: [
|
||||
"pytest",
|
||||
"-m", "wallet",
|
||||
"-m", "wallet or ethclient",
|
||||
"--rpc_url=http://status-go:3333",
|
||||
"--rpc_url_2=http://status-go-no-funds:3333",
|
||||
"--anvil_url=http://anvil:8545",
|
||||
|
|
|
@ -10,3 +10,4 @@ markers =
|
|||
tx
|
||||
wakuext
|
||||
accounts
|
||||
ethclient
|
||||
|
|
|
@ -6,12 +6,14 @@ import jsonschema
|
|||
import time
|
||||
import requests
|
||||
from conftest import option, user_1, user_2
|
||||
|
||||
import pytest
|
||||
from collections import namedtuple
|
||||
|
||||
class RpcTestCase:
|
||||
network_id = 31337
|
||||
|
||||
def setup_method(self):
|
||||
self.network_id = 31337
|
||||
pass
|
||||
|
||||
def _try_except_JSONDecodeError_KeyError(self, response, key: str):
|
||||
try:
|
||||
|
@ -56,13 +58,19 @@ class RpcTestCase:
|
|||
|
||||
return response
|
||||
|
||||
def rpc_valid_request(self, method, params=[], _id=None, client=None, url=None):
|
||||
response = self.rpc_request(method, params, _id, client, url)
|
||||
self.verify_is_valid_json_rpc_response(response, _id)
|
||||
return response
|
||||
|
||||
def verify_json_schema(self, response, method):
|
||||
with open(f"{option.base_dir}/schemas/{method}", "r") as schema:
|
||||
jsonschema.validate(instance=response.json(),
|
||||
schema=json.load(schema))
|
||||
|
||||
|
||||
class TransactionTestCase(RpcTestCase):
|
||||
class WalletTestCase(RpcTestCase):
|
||||
def setup_method(self):
|
||||
super().setup_method()
|
||||
|
||||
def wallet_create_multi_transaction(self, **kwargs):
|
||||
method = "wallet_createMultiTransaction"
|
||||
|
@ -88,7 +96,7 @@ class TransactionTestCase(RpcTestCase):
|
|||
"fromAddress": user_1.address,
|
||||
"fromAmount": "0x5af3107a4000",
|
||||
"fromAsset": "ETH",
|
||||
"multiTxType": "MultiTransactionSend",
|
||||
"type": 0, # MultiTransactionSend
|
||||
"toAddress": user_2.address,
|
||||
"toAsset": "ETH",
|
||||
},
|
||||
|
@ -101,19 +109,66 @@ class TransactionTestCase(RpcTestCase):
|
|||
],
|
||||
f"{option.password}",
|
||||
]
|
||||
response = self.rpc_request(method, params, 13)
|
||||
return response
|
||||
return self.rpc_request(method, params)
|
||||
|
||||
def send_valid_multi_transaction(self, **kwargs):
|
||||
response = self.wallet_create_multi_transaction(**kwargs)
|
||||
|
||||
tx_hash = None
|
||||
self.verify_is_valid_json_rpc_response(response)
|
||||
try:
|
||||
tx_hash = response.json(
|
||||
)["result"]["hashes"][str(self.network_id)][0]
|
||||
except (KeyError, json.JSONDecodeError):
|
||||
raise Exception(response.content)
|
||||
return tx_hash
|
||||
|
||||
class TransactionTestCase(WalletTestCase):
|
||||
def setup_method(self):
|
||||
super().setup_method()
|
||||
|
||||
response = self.wallet_create_multi_transaction()
|
||||
try:
|
||||
self.tx_hash = response.json(
|
||||
)["result"]["hashes"][str(self.network_id)][0]
|
||||
except (KeyError, json.JSONDecodeError):
|
||||
raise Exception(response.content)
|
||||
self.tx_hash = self.send_valid_multi_transaction()
|
||||
|
||||
class EthApiTestCase(WalletTestCase):
|
||||
@pytest.fixture(autouse=True, scope='class')
|
||||
def tx_data(self):
|
||||
tx_hash = self.send_valid_multi_transaction()
|
||||
self.wait_until_tx_not_pending(tx_hash)
|
||||
|
||||
receipt = self.get_transaction_receipt(tx_hash)
|
||||
try:
|
||||
block_number = receipt.json()["result"]["blockNumber"]
|
||||
block_hash = receipt.json()["result"]["blockHash"]
|
||||
except (KeyError, json.JSONDecodeError):
|
||||
raise Exception(receipt.content)
|
||||
|
||||
TxData = namedtuple("TxData", ["tx_hash", "block_number", "block_hash"])
|
||||
return TxData(tx_hash, block_number, block_hash)
|
||||
|
||||
def get_block_header(self, block_number):
|
||||
method = "ethclient_headerByNumber"
|
||||
params = [self.network_id, block_number]
|
||||
return self.rpc_valid_request(method, params)
|
||||
|
||||
def get_transaction_receipt(self, tx_hash):
|
||||
method = "ethclient_transactionReceipt"
|
||||
params = [self.network_id, tx_hash]
|
||||
return self.rpc_valid_request(method, params)
|
||||
|
||||
def wait_until_tx_not_pending(self, tx_hash, timeout=10):
|
||||
method = "ethclient_transactionByHash"
|
||||
params = [self.network_id, tx_hash]
|
||||
response = self.rpc_valid_request(method, params)
|
||||
|
||||
start_time = time.time()
|
||||
while response.json()["result"]["isPending"] == True:
|
||||
time_passed = time.time() - start_time
|
||||
if time_passed >= timeout:
|
||||
raise TimeoutError(
|
||||
f"Tx {tx_hash} is still pending after {timeout} seconds")
|
||||
time.sleep(0.5)
|
||||
response = self.rpc_valid_request(method, params)
|
||||
return response.json()["result"]["tx"]
|
||||
|
||||
class SignalTestCase(RpcTestCase):
|
||||
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import pytest
|
||||
from conftest import option
|
||||
from test_cases import EthApiTestCase
|
||||
|
||||
def validateHeader(header, block_number, block_hash):
|
||||
assert header["number"] == block_number
|
||||
assert header["hash"] == block_hash
|
||||
|
||||
def validateBlock(block, block_number, block_hash, expected_tx_hash):
|
||||
validateHeader(block["header"], block_number, block_hash)
|
||||
tx_hashes = [tx["hash"] for tx in block["transactions"]]
|
||||
assert expected_tx_hash in tx_hashes
|
||||
|
||||
def validateTransaction(tx, tx_hash):
|
||||
assert tx["tx"]["hash"] == tx_hash
|
||||
|
||||
def validateReceipt(receipt, tx_hash, block_number, block_hash):
|
||||
assert receipt["transactionHash"] == tx_hash
|
||||
assert receipt["blockNumber"] == block_number
|
||||
assert receipt["blockHash"] == block_hash
|
||||
|
||||
@pytest.mark.rpc
|
||||
@pytest.mark.ethclient
|
||||
class TestRpc(EthApiTestCase):
|
||||
def test_block_number(self):
|
||||
self.rpc_valid_request("ethclient_blockNumber", [self.network_id])
|
||||
|
||||
def test_suggest_gas_price(self):
|
||||
self.rpc_valid_request("ethclient_suggestGasPrice", [self.network_id])
|
||||
|
||||
def test_header_by_number(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_headerByNumber", [self.network_id, tx_data.block_number])
|
||||
validateHeader(response.json()["result"], tx_data.block_number, tx_data.block_hash)
|
||||
|
||||
def test_block_by_number(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_blockByNumber", [self.network_id, tx_data.block_number])
|
||||
validateBlock(response.json()["result"], tx_data.block_number, tx_data.block_hash, tx_data.tx_hash)
|
||||
|
||||
def test_header_by_hash(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_headerByHash", [self.network_id, tx_data.block_hash])
|
||||
validateHeader(response.json()["result"], tx_data.block_number, tx_data.block_hash)
|
||||
|
||||
def test_block_by_hash(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_blockByHash", [self.network_id, tx_data.block_hash])
|
||||
validateBlock(response.json()["result"], tx_data.block_number, tx_data.block_hash, tx_data.tx_hash)
|
||||
|
||||
def test_transaction_by_hash(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_transactionByHash", [self.network_id, tx_data.tx_hash])
|
||||
validateTransaction(response.json()["result"], tx_data.tx_hash)
|
||||
|
||||
def test_transaction_receipt(self, tx_data):
|
||||
response = self.rpc_valid_request("ethclient_transactionReceipt", [self.network_id, tx_data.tx_hash])
|
||||
validateReceipt(response.json()["result"], tx_data.tx_hash, tx_data.block_number, tx_data.block_hash)
|
Loading…
Reference in New Issue