update fork

This commit is contained in:
0xmuralik
2022-10-10 16:08:33 +05:30
parent 632d53e34a
commit 56d59feaa0
383 changed files with 36784 additions and 21462 deletions
+157 -9
View File
@@ -7,12 +7,18 @@ import (
"math/big"
"testing"
"cosmossdk.io/math"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
"github.com/cerc-io/laconicd/rpc/types"
"github.com/cosmos/cosmos-sdk/client/flags"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
// . "github.com/onsi/ginkgo"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
// . "github.com/onsi/ginkgo/v2"
// . "github.com/onsi/gomega"
sdkmath "cosmossdk.io/math"
"github.com/stretchr/testify/suite"
"github.com/ethereum/go-ethereum"
@@ -132,7 +138,6 @@ func (s *IntegrationTestSuite) TestBlock() {
s.Require().NotNil(blockByNum)
// compare the ethereum header with the tendermint header
s.Require().Equal(len(block.Block.Txs), len(blockByNum.Body().Transactions))
s.Require().Equal(block.Block.LastBlockID.Hash.Bytes(), blockByNum.Header().ParentHash.Bytes())
hash := common.BytesToHash(block.Block.Hash())
@@ -143,7 +148,12 @@ func (s *IntegrationTestSuite) TestBlock() {
blockByHash, err := s.network.Validators[0].JSONRPCClient.BlockByHash(s.ctx, hash)
s.Require().NoError(err)
s.Require().NotNil(blockByHash)
s.Require().Equal(blockByNum, blockByHash)
// Compare blockByNumber and blockByHash results
s.Require().Equal(blockByNum.Hash(), blockByHash.Hash())
s.Require().Equal(blockByNum.Transactions().Len(), blockByHash.Transactions().Len())
s.Require().Equal(blockByNum.ParentHash(), blockByHash.ParentHash())
s.Require().Equal(blockByNum.Root(), blockByHash.Root())
// TODO: parse Tm block to Ethereum and compare
}
@@ -374,7 +384,7 @@ func (s *IntegrationTestSuite) TestGetBalance() {
}
func (s *IntegrationTestSuite) TestGetLogs() {
//TODO create tests to cover different filterQuery params
// TODO create tests to cover different filterQuery params
_, contractAddr := s.deployERC20Contract()
blockNum, err := s.network.Validators[0].JSONRPCClient.BlockNumber(s.ctx)
@@ -400,7 +410,7 @@ func (s *IntegrationTestSuite) TestGetLogs() {
}
func (s *IntegrationTestSuite) TestTransactionReceiptERC20Transfer() {
//start with clean block
// start with clean block
err := s.network.WaitForNextBlock()
s.Require().NoError(err)
// deploy erc20 contract
@@ -567,7 +577,7 @@ func (s *IntegrationTestSuite) deployContract(data []byte) (transaction common.H
// Deploys erc20 contract, commits block and returns contract address
func (s *IntegrationTestSuite) deployERC20Contract() (transaction common.Hash, contractAddr common.Address) {
owner := common.BytesToAddress(s.network.Validators[0].Address)
supply := math.NewIntWithDecimal(1000, 18).BigInt()
supply := sdkmath.NewIntWithDecimal(1000, 18).BigInt()
ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", owner, supply)
s.Require().NoError(err)
@@ -633,7 +643,6 @@ func (s *IntegrationTestSuite) transferERC20Transaction(contractAddr, to common.
receipt := s.expectSuccessReceipt(ercTransferTx.AsTransaction().Hash())
s.Require().NotEmpty(receipt.Logs)
return ercTransferTx.AsTransaction().Hash()
}
func (s *IntegrationTestSuite) storeValueStorageContract(contractAddr common.Address, amount *big.Int) common.Hash {
@@ -747,3 +756,142 @@ func (s *IntegrationTestSuite) TestPendingTransactionFilter() {
s.Require().NoError(err)
s.Require().Equal([]common.Hash{signedTx.Hash()}, filterResult)
}
// TODO: add transactionIndex tests once we have OpenRPC interfaces
func (s *IntegrationTestSuite) TestBatchETHTransactions() {
const ethTxs = 2
txBuilder := s.network.Validators[0].ClientCtx.TxConfig.NewTxBuilder()
builder, ok := txBuilder.(authtx.ExtensionOptionsTxBuilder)
s.Require().True(ok)
recipient := common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec")
accountNonce := s.getAccountNonce(recipient)
feeAmount := sdk.ZeroInt()
var gasLimit uint64
var msgs []sdk.Msg
for i := 0; i < ethTxs; i++ {
chainId, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx)
s.Require().NoError(err)
gasPrice := s.getGasPrice()
from := common.BytesToAddress(s.network.Validators[0].Address)
nonce := accountNonce + uint64(i) + 1
msgTx := evmtypes.NewTx(
chainId,
nonce,
&recipient,
big.NewInt(10),
100000,
gasPrice,
big.NewInt(200),
nil,
nil,
nil,
)
msgTx.From = from.Hex()
err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring)
s.Require().NoError(err)
// A valid msg should have empty `From`
msgTx.From = ""
msgs = append(msgs, msgTx.GetMsgs()...)
txData, err := evmtypes.UnpackTxData(msgTx.Data)
s.Require().NoError(err)
feeAmount = feeAmount.Add(sdkmath.NewIntFromBigInt(txData.Fee()))
gasLimit = gasLimit + txData.GetGas()
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
s.Require().NoError(err)
queryClient := types.NewQueryClient(s.network.Validators[0].ClientCtx)
res, err := queryClient.Params(s.ctx, &evmtypes.QueryParamsRequest{})
fees := make(sdk.Coins, 0)
if feeAmount.Sign() > 0 {
fees = fees.Add(sdk.Coin{Denom: res.Params.EvmDenom, Amount: feeAmount})
}
builder.SetExtensionOptions(option)
err = builder.SetMsgs(msgs...)
s.Require().NoError(err)
builder.SetFeeAmount(fees)
builder.SetGasLimit(gasLimit)
tx := builder.GetTx()
txEncoder := s.network.Validators[0].ClientCtx.TxConfig.TxEncoder()
txBytes, err := txEncoder(tx)
s.Require().NoError(err)
syncCtx := s.network.Validators[0].ClientCtx.WithBroadcastMode(flags.BroadcastBlock)
txResponse, err := syncCtx.BroadcastTx(txBytes)
s.Require().NoError(err)
s.Require().Equal(uint32(0), txResponse.Code)
block, err := s.network.Validators[0].JSONRPCClient.BlockByNumber(s.ctx, big.NewInt(txResponse.Height))
s.Require().NoError(err)
txs := block.Transactions()
s.Require().Len(txs, ethTxs)
for i, tx := range txs {
s.Require().Equal(accountNonce+uint64(i)+1, tx.Nonce())
}
}
func (s *IntegrationTestSuite) TestGasConsumptionOnNormalTransfer() {
testCases := []struct {
name string
gasLimit uint64
expectedGasUsed uint64
}{
{
"gas used is the same as gas limit",
21000,
21000,
},
{
"gas used is half of Gas limit",
70000,
35000,
},
{
"gas used is less than half of gasLimit",
30000,
21000,
},
}
recipient := common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec")
chainID, err := s.network.Validators[0].JSONRPCClient.ChainID(s.ctx)
s.Require().NoError(err)
from := common.BytesToAddress(s.network.Validators[0].Address)
for _, tc := range testCases {
s.Run(tc.name, func() {
nonce := s.getAccountNonce(from)
s.Require().NoError(err)
gasPrice := s.getGasPrice()
msgTx := evmtypes.NewTx(
chainID,
nonce,
&recipient,
nil,
tc.gasLimit,
gasPrice,
nil, nil,
nil,
nil,
)
msgTx.From = from.Hex()
err = msgTx.Sign(s.ethSigner, s.network.Validators[0].ClientCtx.Keyring)
s.Require().NoError(err)
err := s.network.Validators[0].JSONRPCClient.SendTransaction(s.ctx, msgTx.AsTransaction())
s.Require().NoError(err)
s.waitForTransaction()
receipt := s.expectSuccessReceipt(msgTx.AsTransaction().Hash())
s.Equal(receipt.GasUsed, tc.expectedGasUsed)
})
}
}
+6 -2
View File
@@ -96,8 +96,12 @@ func (cc *ChainContext) Finalize(
// Note: The block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
// TODO: Figure out if this needs to be hooked up to any part of the ABCI?
func (cc *ChainContext) FinalizeAndAssemble(_ ethcons.ChainHeaderReader, _ *ethtypes.Header, _ *ethstate.StateDB, _ []*ethtypes.Transaction,
_ []*ethtypes.Header, _ []*ethtypes.Receipt,
func (cc *ChainContext) FinalizeAndAssemble(_ ethcons.ChainHeaderReader,
_ *ethtypes.Header,
_ *ethstate.StateDB,
_ []*ethtypes.Transaction,
_ []*ethtypes.Header,
_ []*ethtypes.Receipt,
) (*ethtypes.Block, error) {
return nil, nil
}
+2 -2
View File
@@ -56,10 +56,10 @@ type ImporterTestSuite struct {
ctx sdk.Context
}
/// DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
// / DoSetupTest setup test environment, it uses`require.TestingT` to support both `testing.T` and `testing.B`.
func (suite *ImporterTestSuite) DoSetupTest(t require.TestingT) {
checkTx := false
suite.app = app.Setup(suite.T(), checkTx, nil)
suite.app = app.Setup(checkTx, nil)
// consensus key
priv, err := ethsecp256k1.GenerateKey()
require.NoError(t, err)
+9
View File
@@ -0,0 +1,9 @@
[settings]
# compatible with black
# https://black.readthedocs.io/en/stable/the_black_code_style.html
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88
+55
View File
@@ -0,0 +1,55 @@
# RPC Integration tests
The RPC integration test suite uses nix for reproducible and configurable
builds allowing to run integration tests using python web3 library against
different Ethermint and [Geth](https://github.com/ethereum/go-ethereum) clients with multiple configurations.
## Installation
Nix Multi-user installation:
```
sh <(curl -L https://nixos.org/nix/install) --daemon
```
Make sure the following line has been added to your shell profile (e.g. ~/.profile):
```
source ~/.nix-profile/etc/profile.d/nix.sh
```
Then re-login shell, the nix installation is completed.
For linux:
```
sh <(curl -L https://nixos.org/nix/install) --no-daemon
```
## Run Local
First time run (can take a while):
```
make run-integration-tests
```
Once you've run them once and, you can run:
```
nix-shell tests/integration_tests/shell.nix
cd tests/integration_tests
pytest -s -vv
```
If you're changing anything on the ethermint rpc, rerun the first command.
## Caching
You can enable Binary Cache to speed up the tests:
```
$ nix-env -iA cachix -f https://cachix.org/api/v1/install
$ cachix use ethermint
```
View File
@@ -0,0 +1,8 @@
{ pkgs ? import ../../../nix { } }:
let ethermintd = (pkgs.callPackage ../../../. { });
in
ethermintd.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [ ] ++ [
./broken-ethermintd.patch
];
})
@@ -0,0 +1,15 @@
diff --git a/app/app.go b/app/app.go
index 158bf7a3..a3b5718c 100644
--- a/app/app.go
+++ b/app/app.go
@@ -681,6 +681,10 @@ func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBloc
// EndBlocker updates every end block
func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
+ if ctx.BlockHeight()%10 == 0 {
+ store := ctx.KVStore(app.keys["evm"])
+ store.Set([]byte("hello"), []byte("world"))
+ }
return app.mm.EndBlock(ctx, req)
}
@@ -0,0 +1,17 @@
local config = import 'default.jsonnet';
config {
'ethermint_9000-1'+: {
genesis+: {
app_state+: {
feemarket+: {
params+: {
no_base_fee: false,
base_fee:: super.base_fee,
initial_base_fee: super.base_fee,
},
},
},
},
},
}
@@ -0,0 +1,92 @@
{
dotenv: '../../../scripts/.env',
'ethermint_9000-1': {
cmd: 'ethermintd',
'start-flags': '--trace',
config: {
consensus: {
// larger timeout for more stable mempool tests
timeout_commit: '10s',
},
mempool: {
// use v1 mempool to enable tx prioritization
version: 'v1',
},
},
'app-config': {
'minimum-gas-prices': '0aphoton',
'index-events': ['ethereum_tx.ethereumTxHash'],
'json-rpc': {
address: '0.0.0.0:{EVMRPC_PORT}',
'ws-address': '0.0.0.0:{EVMRPC_PORT_WS}',
api: 'eth,net,web3,debug',
'feehistory-cap': 100,
'block-range-cap': 10000,
'logs-cap': 10000,
},
},
validators: [{
coins: '1000000000000000000stake,10000000000000000000000aphoton',
staked: '1000000000000000000stake',
mnemonic: '${VALIDATOR1_MNEMONIC}',
}, {
coins: '1000000000000000000stake,10000000000000000000000aphoton',
staked: '1000000000000000000stake',
mnemonic: '${VALIDATOR2_MNEMONIC}',
}],
accounts: [{
name: 'community',
coins: '10000000000000000000000aphoton',
mnemonic: '${COMMUNITY_MNEMONIC}',
}, {
name: 'signer1',
coins: '20000000000000000000000aphoton',
mnemonic: '${SIGNER1_MNEMONIC}',
}, {
name: 'signer2',
coins: '30000000000000000000000aphoton',
mnemonic: '${SIGNER2_MNEMONIC}',
}],
genesis: {
consensus_params: {
block: {
max_bytes: '1048576',
max_gas: '81500000',
},
},
app_state: {
evm: {
params: {
evm_denom: 'aphoton',
},
},
gov: {
voting_params: {
voting_period: '10s',
},
deposit_params: {
max_deposit_period: '10s',
min_deposit: [
{
denom: 'aphoton',
amount: '1',
},
],
},
},
transfer: {
params: {
receive_enabled: true,
send_enabled: true,
},
},
feemarket: {
params: {
no_base_fee: false,
base_fee: '100000000000',
},
},
},
},
},
}
@@ -0,0 +1,20 @@
local config = import 'default.jsonnet';
config {
'ethermint_9000-1'+: {
config+: {
tx_index+: {
indexer: 'null',
},
},
'app-config'+: {
pruning: 'everything',
'state-sync'+: {
'snapshot-interval': 0,
},
'json-rpc'+: {
'enable-indexer': true,
},
},
},
}
@@ -0,0 +1,12 @@
local config = import 'default.jsonnet';
config {
'ethermint_9000-1'+: {
'app-config'+: {
pruning: 'everything',
'state-sync'+: {
'snapshot-interval': 0,
},
},
},
}
@@ -0,0 +1,9 @@
local config = import 'default.jsonnet';
config {
'ethermint_9000-1'+: {
validators: super.validators + [{
name: 'fullnode',
}],
},
}
@@ -0,0 +1,17 @@
let
pkgs = import ../../../nix { };
fetchEthermint = rev: builtins.fetchTarball "https://github.com/evmos/ethermint/archive/${rev}.tar.gz";
released = pkgs.buildGo118Module rec {
name = "ethermintd";
# the commit before https://github.com/evmos/ethermint/pull/943
src = fetchEthermint "f21592ebfe74da7590eb42ed926dae970b2a9a3f";
subPackages = [ "cmd/ethermintd" ];
vendorSha256 = "sha256-ABm5t6R/u2S6pThGrgdsqe8n3fH5tIWw7a57kxJPbYw=";
doCheck = false;
};
current = pkgs.callPackage ../../../. { };
in
pkgs.linkFarm "upgrade-test-package" [
{ name = "genesis"; path = released; }
{ name = "integration-test-upgrade"; path = current; }
]
+47
View File
@@ -0,0 +1,47 @@
from pathlib import Path
import pytest
from .network import setup_custom_ethermint, setup_ethermint, setup_geth
@pytest.fixture(scope="session")
def ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("ethermint")
yield from setup_ethermint(path, 26650)
@pytest.fixture(scope="session")
def ethermint_indexer(tmp_path_factory):
path = tmp_path_factory.mktemp("indexer")
yield from setup_custom_ethermint(
path, 26660, Path(__file__).parent / "configs/enable-indexer.jsonnet"
)
@pytest.fixture(scope="session")
def geth(tmp_path_factory):
path = tmp_path_factory.mktemp("geth")
yield from setup_geth(path, 8545)
@pytest.fixture(
scope="session", params=["ethermint", "geth", "ethermint-ws", "enable-indexer"]
)
def cluster(request, ethermint, ethermint_indexer, geth):
"""
run on both ethermint and geth
"""
provider = request.param
if provider == "ethermint":
yield ethermint
elif provider == "geth":
yield geth
elif provider == "ethermint-ws":
ethermint_ws = ethermint.copy()
ethermint_ws.use_websocket()
yield ethermint_ws
elif provider == "enable-indexer":
yield ethermint_indexer
else:
raise NotImplementedError
@@ -0,0 +1,11 @@
node_modules
.env
coverage
coverage.json
typechain
typechain-types
#Hardhat files
cache
artifacts
@@ -0,0 +1,13 @@
# Sample Hardhat Project
This project demonstrates a basic Hardhat use case. It comes with a sample contract, a test for that contract, and a script that deploys that contract.
Try running some of the following tasks:
```shell
npx hardhat help
npx hardhat test
GAS_REPORT=true npx hardhat test
npx hardhat node
npx hardhat run scripts/deploy.js
```
@@ -0,0 +1,20 @@
pragma solidity >0.5.0;
contract Greeter {
string public greeting;
event ChangeGreeting(address from, string value);
constructor() public {
greeting = "Hello";
}
function setGreeting(string memory _greeting) public {
greeting = _greeting;
emit ChangeGreeting(msg.sender, _greeting);
}
function greet() public view returns (string memory) {
return greeting;
}
}
@@ -0,0 +1,9 @@
pragma solidity 0.8.10;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract TestERC20A is ERC20 {
constructor() public ERC20("TestERC20", "Test") {
_mint(msg.sender, 100000000000000000000000000);
}
}
@@ -0,0 +1,23 @@
import { HardhatUserConfig } from "hardhat/config";
import "hardhat-typechain";
const config: HardhatUserConfig = {
solidity: {
compilers: [
{
version: "0.8.10",
settings: {
optimizer: {
enabled: true
}
}
},
],
},
typechain: {
outDir: "typechain",
target: "ethers-v5",
},
};
export default config;
+834
View File
@@ -0,0 +1,834 @@
import json
import tempfile
import requests
from dateutil.parser import isoparse
from pystarport.utils import build_cli_args_safe, interact
DEFAULT_GAS_PRICE = "5000000000000aphoton"
class ChainCommand:
def __init__(self, cmd):
self.cmd = cmd
def __call__(self, cmd, *args, stdin=None, **kwargs):
"execute chain-maind"
args = " ".join(build_cli_args_safe(cmd, *args, **kwargs))
return interact(f"{self.cmd} {args}", input=stdin)
class CosmosCLI:
"the apis to interact with wallet and blockchain"
def __init__(
self,
data_dir,
node_rpc,
cmd,
):
self.data_dir = data_dir
self._genesis = json.loads(
(self.data_dir / "config" / "genesis.json").read_text()
)
self.chain_id = self._genesis["chain_id"]
self.node_rpc = node_rpc
self.raw = ChainCommand(cmd)
self.output = None
self.error = None
@property
def node_rpc_http(self):
return "http" + self.node_rpc.removeprefix("tcp")
def node_id(self):
"get tendermint node id"
output = self.raw("tendermint", "show-node-id", home=self.data_dir)
return output.decode().strip()
def delete_account(self, name):
"delete wallet account in node's keyring"
return self.raw(
"keys",
"delete",
name,
"-y",
"--force",
home=self.data_dir,
output="json",
keyring_backend="test",
)
def create_account(self, name, mnemonic=None):
"create new keypair in node's keyring"
if mnemonic is None:
output = self.raw(
"keys",
"add",
name,
home=self.data_dir,
output="json",
keyring_backend="test",
)
else:
output = self.raw(
"keys",
"add",
name,
"--recover",
home=self.data_dir,
output="json",
keyring_backend="test",
stdin=mnemonic.encode() + b"\n",
)
return json.loads(output)
def init(self, moniker):
"the node's config is already added"
return self.raw(
"init",
moniker,
chain_id=self.chain_id,
home=self.data_dir,
)
def validate_genesis(self):
return self.raw("validate-genesis", home=self.data_dir)
def add_genesis_account(self, addr, coins, **kwargs):
return self.raw(
"add-genesis-account",
addr,
coins,
home=self.data_dir,
output="json",
**kwargs,
)
def gentx(self, name, coins, min_self_delegation=1, pubkey=None):
return self.raw(
"gentx",
name,
coins,
min_self_delegation=str(min_self_delegation),
home=self.data_dir,
chain_id=self.chain_id,
keyring_backend="test",
pubkey=pubkey,
)
def collect_gentxs(self, gentx_dir):
return self.raw("collect-gentxs", gentx_dir, home=self.data_dir)
def status(self):
return json.loads(self.raw("status", node=self.node_rpc))
def block_height(self):
return int(self.status()["SyncInfo"]["latest_block_height"])
def block_time(self):
return isoparse(self.status()["SyncInfo"]["latest_block_time"])
def balances(self, addr):
return json.loads(
self.raw("query", "bank", "balances", addr, home=self.data_dir)
)["balances"]
def balance(self, addr, denom="aphoton"):
denoms = {coin["denom"]: int(coin["amount"]) for coin in self.balances(addr)}
return denoms.get(denom, 0)
def query_tx(self, tx_type, tx_value):
tx = self.raw(
"query",
"tx",
"--type",
tx_type,
tx_value,
home=self.data_dir,
chain_id=self.chain_id,
node=self.node_rpc,
)
return json.loads(tx)
def query_all_txs(self, addr):
txs = self.raw(
"query",
"txs-all",
addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return json.loads(txs)
def distribution_commission(self, addr):
coin = json.loads(
self.raw(
"query",
"distribution",
"commission",
addr,
output="json",
node=self.node_rpc,
)
)["commission"][0]
return float(coin["amount"])
def distribution_community(self):
coin = json.loads(
self.raw(
"query",
"distribution",
"community-pool",
output="json",
node=self.node_rpc,
)
)["pool"][0]
return float(coin["amount"])
def distribution_reward(self, delegator_addr):
coin = json.loads(
self.raw(
"query",
"distribution",
"rewards",
delegator_addr,
output="json",
node=self.node_rpc,
)
)["total"][0]
return float(coin["amount"])
def address(self, name, bech="acc"):
output = self.raw(
"keys",
"show",
name,
"-a",
home=self.data_dir,
keyring_backend="test",
bech=bech,
)
return output.strip().decode()
def account(self, addr):
return json.loads(
self.raw(
"query", "auth", "account", addr, output="json", node=self.node_rpc
)
)
def tx_search(self, events: str):
"/tx_search"
return json.loads(
self.raw("query", "txs", events=events, output="json", node=self.node_rpc)
)
def tx_search_rpc(self, events: str):
rsp = requests.get(
f"{self.node_rpc_http}/tx_search",
params={
"query": f'"{events}"',
},
).json()
assert "error" not in rsp, rsp["error"]
return rsp["result"]["txs"]
def tx(self, value, **kwargs):
"/tx"
default_kwargs = {
"home": self.data_dir,
}
return json.loads(self.raw("query", "tx", value, **(default_kwargs | kwargs)))
def total_supply(self):
return json.loads(
self.raw("query", "bank", "total", output="json", node=self.node_rpc)
)
def validator(self, addr):
return json.loads(
self.raw(
"query",
"staking",
"validator",
addr,
output="json",
node=self.node_rpc,
)
)
def validators(self):
return json.loads(
self.raw(
"query", "staking", "validators", output="json", node=self.node_rpc
)
)["validators"]
def staking_params(self):
return json.loads(
self.raw("query", "staking", "params", output="json", node=self.node_rpc)
)
def staking_pool(self, bonded=True):
return int(
json.loads(
self.raw("query", "staking", "pool", output="json", node=self.node_rpc)
)["bonded_tokens" if bonded else "not_bonded_tokens"]
)
def transfer(self, from_, to, coins, generate_only=False, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
"tx",
"bank",
"send",
from_,
to,
coins,
"-y",
"--generate-only" if generate_only else None,
home=self.data_dir,
**kwargs,
)
)
def get_delegated_amount(self, which_addr):
return json.loads(
self.raw(
"query",
"staking",
"delegations",
which_addr,
home=self.data_dir,
chain_id=self.chain_id,
node=self.node_rpc,
output="json",
)
)
def delegate_amount(self, to_addr, amount, from_addr, gas_price=None):
if gas_price is None:
return json.loads(
self.raw(
"tx",
"staking",
"delegate",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
else:
return json.loads(
self.raw(
"tx",
"staking",
"delegate",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
gas_prices=gas_price,
)
)
# to_addr: croclcl1... , from_addr: cro1...
def unbond_amount(self, to_addr, amount, from_addr):
return json.loads(
self.raw(
"tx",
"staking",
"unbond",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
# to_validator_addr: crocncl1... , from_from_validator_addraddr: crocl1...
def redelegate_amount(
self, to_validator_addr, from_validator_addr, amount, from_addr
):
return json.loads(
self.raw(
"tx",
"staking",
"redelegate",
from_validator_addr,
to_validator_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
# from_delegator can be account name or address
def withdraw_all_rewards(self, from_delegator):
return json.loads(
self.raw(
"tx",
"distribution",
"withdraw-all-rewards",
"-y",
from_=from_delegator,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def make_multisig(self, name, signer1, signer2):
self.raw(
"keys",
"add",
name,
multisig=f"{signer1},{signer2}",
multisig_threshold="2",
home=self.data_dir,
keyring_backend="test",
)
def sign_multisig_tx(self, tx_file, multi_addr, signer_name):
return json.loads(
self.raw(
"tx",
"sign",
tx_file,
from_=signer_name,
multisig=multi_addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def sign_batch_multisig_tx(
self, tx_file, multi_addr, signer_name, account_number, sequence_number
):
r = self.raw(
"tx",
"sign-batch",
"--offline",
tx_file,
account_number=account_number,
sequence=sequence_number,
from_=signer_name,
multisig=multi_addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return r.decode("utf-8")
def encode_signed_tx(self, signed_tx):
return self.raw(
"tx",
"encode",
signed_tx,
)
def sign_tx(self, tx_file, signer):
return json.loads(
self.raw(
"tx",
"sign",
tx_file,
from_=signer,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def sign_tx_json(self, tx, signer, max_priority_price=None):
if max_priority_price is not None:
tx["body"]["extension_options"].append(
{
"@type": "/ethermint.types.v1.ExtensionOptionDynamicFeeTx",
"max_priority_price": str(max_priority_price),
}
)
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(tx, fp)
fp.flush()
return self.sign_tx(fp.name, signer)
def combine_multisig_tx(self, tx_file, multi_name, signer1_file, signer2_file):
return json.loads(
self.raw(
"tx",
"multisign",
tx_file,
multi_name,
signer1_file,
signer2_file,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def combine_batch_multisig_tx(
self, tx_file, multi_name, signer1_file, signer2_file
):
r = self.raw(
"tx",
"multisign-batch",
tx_file,
multi_name,
signer1_file,
signer2_file,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return r.decode("utf-8")
def broadcast_tx(self, tx_file, **kwargs):
kwargs.setdefault("broadcast_mode", "block")
kwargs.setdefault("output", "json")
return json.loads(
self.raw("tx", "broadcast", tx_file, node=self.node_rpc, **kwargs)
)
def broadcast_tx_json(self, tx, **kwargs):
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(tx, fp)
fp.flush()
return self.broadcast_tx(fp.name, **kwargs)
def unjail(self, addr):
return json.loads(
self.raw(
"tx",
"slashing",
"unjail",
"-y",
from_=addr,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def create_validator(
self,
amount,
moniker=None,
commission_max_change_rate="0.01",
commission_rate="0.1",
commission_max_rate="0.2",
min_self_delegation="1",
identity="",
website="",
security_contact="",
details="",
):
"""MsgCreateValidator
create the node with create_node before call this"""
pubkey = (
"'"
+ (
self.raw(
"tendermint",
"show-validator",
home=self.data_dir,
)
.strip()
.decode()
)
+ "'"
)
return json.loads(
self.raw(
"tx",
"staking",
"create-validator",
"-y",
from_=self.address("validator"),
amount=amount,
pubkey=pubkey,
min_self_delegation=min_self_delegation,
# commision
commission_rate=commission_rate,
commission_max_rate=commission_max_rate,
commission_max_change_rate=commission_max_change_rate,
# description
moniker=moniker,
identity=identity,
website=website,
security_contact=security_contact,
details=details,
# basic
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def edit_validator(
self,
commission_rate=None,
moniker=None,
identity=None,
website=None,
security_contact=None,
details=None,
):
"""MsgEditValidator"""
options = dict(
commission_rate=commission_rate,
# description
moniker=moniker,
identity=identity,
website=website,
security_contact=security_contact,
details=details,
)
return json.loads(
self.raw(
"tx",
"staking",
"edit-validator",
"-y",
from_=self.address("validator"),
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
**{k: v for k, v in options.items() if v is not None},
)
)
def gov_propose(self, proposer, kind, proposal, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
if kind == "software-upgrade":
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
proposal["name"],
"-y",
from_=proposer,
# content
title=proposal.get("title"),
description=proposal.get("description"),
upgrade_height=proposal.get("upgrade-height"),
upgrade_time=proposal.get("upgrade-time"),
upgrade_info=proposal.get("upgrade-info"),
deposit=proposal.get("deposit"),
# basic
home=self.data_dir,
**kwargs,
)
)
elif kind == "cancel-software-upgrade":
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
"-y",
from_=proposer,
# content
title=proposal.get("title"),
description=proposal.get("description"),
deposit=proposal.get("deposit"),
# basic
home=self.data_dir,
**kwargs,
)
)
else:
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(proposal, fp)
fp.flush()
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
fp.name,
"-y",
from_=proposer,
# basic
home=self.data_dir,
**kwargs,
)
)
def gov_vote(self, voter, proposal_id, option, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
"tx",
"gov",
"vote",
proposal_id,
option,
"-y",
from_=voter,
home=self.data_dir,
**kwargs,
)
)
def gov_deposit(self, depositor, proposal_id, amount):
return json.loads(
self.raw(
"tx",
"gov",
"deposit",
proposal_id,
amount,
"-y",
from_=depositor,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def query_proposals(self, depositor=None, limit=None, status=None, voter=None):
return json.loads(
self.raw(
"query",
"gov",
"proposals",
depositor=depositor,
count_total=limit,
status=status,
voter=voter,
output="json",
node=self.node_rpc,
)
)
def query_proposal(self, proposal_id):
return json.loads(
self.raw(
"query",
"gov",
"proposal",
proposal_id,
output="json",
node=self.node_rpc,
)
)
def query_tally(self, proposal_id):
return json.loads(
self.raw(
"query",
"gov",
"tally",
proposal_id,
output="json",
node=self.node_rpc,
)
)
def ibc_transfer(
self,
from_,
to,
amount,
channel, # src channel
target_version, # chain version number of target chain
i=0,
):
return json.loads(
self.raw(
"tx",
"ibc-transfer",
"transfer",
"transfer", # src port
channel,
to,
amount,
"-y",
# FIXME https://github.com/cosmos/cosmos-sdk/issues/8059
"--absolute-timeouts",
from_=from_,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
packet_timeout_height=f"{target_version}-10000000000",
packet_timeout_timestamp=0,
)
)
def export(self):
return self.raw("export", home=self.data_dir)
def unsaferesetall(self):
return self.raw("unsafe-reset-all")
def build_evm_tx(self, raw_tx: str, **kwargs):
return json.loads(
self.raw(
"tx",
"evm",
"raw",
raw_tx,
"-y",
"--generate-only",
home=self.data_dir,
**kwargs,
)
)
def query_base_fee(self, **kwargs):
default_kwargs = {"home": self.data_dir}
return int(
json.loads(
self.raw(
"q",
"feemarket",
"base-fee",
**(default_kwargs | kwargs),
)
)["base_fee"]
)
def rollback(self):
self.raw("rollback", home=self.data_dir)
def migrate_keystore(self):
return self.raw("keys", "migrate", home=self.data_dir)
@@ -0,0 +1,76 @@
EXPECTED_GET_STORAGE_AT = (
"0x00000000000000000000000000000000000000000000000000120a0b063499d4"
)
EXPECTED_GET_PROOF = {
"address": "0x4CB06C43fcdABeA22541fcF1F856A6a296448B6c",
"accountProof": [
"0xf90211a03841a7ddd65c70c94b8efa79190d00f0ab134b26f18dcad508f60a7e74559d0ba0464b07429a05039e22931492d6c6251a860c018ea390045d596b1ac11b5c7aa7a011f4b89823a03c9c4b5a8ab079ee1bc0e2a83a508bb7a5dc7d7fb4f2e95d3186a0b5f7c51c3b2d51d97f171d2b38a4df1a7c0acc5eb0de46beeff4d07f5ed20e19a0b591a2ce02367eda31cf2d16eca7c27fd44dbf0864b64ea8259ad36696eb2a04a02b646a7552b8392ae94263757f699a27d6e9176b4c06b9fc0a722f893b964795a02df05d68bceb88eebf68aafde61d10ab942097afc1c58b8435ffd3895358a742a0c2f16143c4d1db03276c433696dddb3e9f3b113bcd854b127962262e98f43147a0828820316cc02bfefd899aba41340659fd06df1e0a0796287ec2a4110239f6d2a050496598670b04df7bbff3718887fa36437d6d8c7afb4eff86f76c5c7097dcc4a0c14e9060c6b3784e35b9e6ae2ad2984142a75910ccc89eb89dc1e2f44b6c58c2a009804db571d0ce07913e1cbacc4f1dc4fb8265c936f5c612e3a47e91c64d8e9fa063d96f38b3cb51b1665c6641e25ffe24803f2941e5df79942f6a53b7169647e4a0899f71abb18c6c956118bf567fac629b75f7e9526873e429d3d8abb6dbb58021a00fd717235298742623c0b3cafb3e4bd86c0b5ab1f71097b4dd19f3d6925d758da0096437146c16097f2ccc1d3e910d65a4132803baee2249e72c8bf0bcaaeb37e580", # noqa: E501
"0xf90151a097b17a89fd2c03ee98cb6459c08f51b269da5cee46650e84470f62bf83b43efe80a03b269d284a4c3cf8f8deacafb637c6d77f607eec8d75e8548d778e629612310480a01403217a7f1416830c870087c524dabade3985271f6f369a12b010883c71927aa0f592ac54c879817389663be677166f5022943e2fe1b52617a1d15c2f353f27dda0ac8d015a9e668f5877fcc391fae33981c00577096f0455b42df4f8e8089ece24a003ba34a13e2f2fb4bf7096540b42d4955c5269875b9cf0f7b87632585d44c9a580a0b179e3230b07db294473ae57f0170262798f8c551c755b5665ace1215cee10ca80a0552d24252639a6ae775aa1df700ffb92c2411daea7286f158d44081c8172d072a0772a87d08cf38c4c68bfde770968571abd16fd3835cb902486bd2e515d53c12d80a0413774f3d900d2d2be7a3ad999ffa859a471dc03a74fb9a6d8275455f5496a548080", # noqa: E501
"0xf869a020d13b52a61d3c1325ce3626a51418adebd6323d4840f1bdd93906359d11c933b846f8440180a01ab7c0b0a2a4bbb5a1495da8c142150891fc64e0c321e1feb70bd5f881951f7ea0551332d96d085185ab4019ad8bcf89c45321e136c261eb6271e574a2edf1461f", # noqa: E501
],
"balance": 0,
"codeHash": "0x551332d96d085185ab4019ad8bcf89c45321e136c261eb6271e574a2edf1461f", # noqa: E501
"nonce": 1,
"storageHash": "0x1ab7c0b0a2a4bbb5a1495da8c142150891fc64e0c321e1feb70bd5f881951f7e", # noqa: E501
"storageProof": [
{
"key": "0x00",
"value": "0x48656c6c6f00000000000000000000000000000000000000000000000000000a", # noqa: E501
"proof": [
"0xf9019180a01ace80e7bed79fbadbe390876bd1a7d9770edf9462049ef8f4b555d05715d53ea049347a3c2eac6525a3fd7e3454dab19d73b4adeb9aa27d29493b9843f3f88814a085079b4abcd07fd4a5d6c52d35f4c4574aecc85830e90c478ca8c18fcbe590de80a02e3f8ad7ea29e784007f51852b9c3e470aef06b11bac32586a8b691134e4c27da064d2157a14bc31f195f73296ea4dcdbe7698edbf3ca81c44bf7730179d98d94ca09e7dc2597c9b7f72ddf84d7eebb0fe2a2fa2ab54fe668cd14fee44d9b40b1a53a0aa5d4acc7ac636d16bc9655556770bc325e1901fb62dc53770ef9110009e080380a0d5fde962bd2fb5326ddc7a9ca7fe0ee47c5bb3227f838b6d73d3299c22457596a08691410eff46b88f929ef649ea25025f62a5362ca8dc8876e5e1f4fc8e79256d80a0673e88d3a8a4616f676793096b5ae87cff931bd20fb8dd466f97809a1126aad8a08b774a45c2273553e2daf4bbc3a8d44fb542ea29b6f125098f79a4d211b3309ca02fed3139c1791269acb9365eddece93e743900eba6b42a6a8614747752ba268f80", # noqa: E501
"0xf891808080a0c7d094301e0c54da37b696d85f72de5520b224ab2cf4f045d8db1a3374caf0488080a0fc5581783bfe27fab9423602e1914d719fd71433e9d7dd63c95fe7e58d10c9c38080a0c64f346fc7a21f6679cba8abdf37ca2de8c4fcd8f8bcaedb261b5f77627c93908080808080a0ddef2936a67a3ac7d3d4ff15a935a45f2cc4976c8f0310aed85daf763780e2b480", # noqa: E501
"0xf843a0200decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563a1a048656c6c6f00000000000000000000000000000000000000000000000000000a", # noqa: E501
],
}
],
}
EXPECTED_GET_TRANSACTION = {
"jsonrpc": "2.0",
"id": 0,
"result": {
"hash": "0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b", # noqa: E501
"blockHash": "0x1d59ff54b1eb26b013ce3cb5fc9dab3705b415a67127a003c3e61eb445bb8df2", # noqa: E501
"blockNumber": "0x5daf3b",
"from": "0xa7d9ddbe1f17865597fbd27ec712455208b6b76d",
"gas": "0xc350",
"gasPrice": "0x4a817c800",
"input": "0x68656c6c6f21",
"nonce": "0x15",
"r": "0x1b5e176d927f8e9ab405058b2d2457392da3e20f328b16ddabcebc33eaac5fea",
"s": "0x4ba69724e8f69de52f0125ad8b3c5c2cef33019bac3249e2c0a2192766d1721c",
"to": "0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
"transactionIndex": "0x41",
"v": "0x25",
"value": "0xf3dbb76162000",
},
}
EXPECTED_FEE_HISTORY = {
"oldestBlock": 3,
"reward": [[220, 7145389], [1000000, 6000213], [550, 550], [125, 12345678]],
"baseFeePerGas": [202583058, 177634473, 155594425, 136217133, 119442408],
"gasUsedRatio": [
0.007390479689642084,
0.0036988514889990873,
0.0018512333048507866,
0.00741217041320997,
],
}
EXPECTED_GET_TRANSACTION_RECEIPT = {
"blockHash": "0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
"blockNumber": 46147,
"contractAddress": None,
"cumulativeGasUsed": 21000,
"from": "0xA1E4380A3B1f749673E270229993eE55F35663b4",
"gasUsed": 21000,
"logs": [],
"logsBloom": "0x000000000000000000000000000000000000000000000000...0000",
"status": 1, # 0 or 1
"to": "0x5DF9B87991262F6BA471F09758CDE1c0FC1De734",
"transactionHash": "0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060", # noqa: E501
"transactionIndex": 0,
}
+137
View File
@@ -0,0 +1,137 @@
import json
import os
import signal
import subprocess
from pathlib import Path
import web3
from pystarport import ports
from web3.middleware import geth_poa_middleware
from .cosmoscli import CosmosCLI
from .utils import wait_for_port
DEFAULT_CHAIN_BINARY = "ethermintd"
class Ethermint:
def __init__(self, base_dir, chain_binary=DEFAULT_CHAIN_BINARY):
self._w3 = None
self.base_dir = base_dir
self.config = json.loads((base_dir / "config.json").read_text())
self.enable_auto_deployment = False
self._use_websockets = False
self.chain_binary = chain_binary
def copy(self):
return Ethermint(self.base_dir)
@property
def w3_http_endpoint(self, i=0):
port = ports.evmrpc_port(self.base_port(i))
return f"http://localhost:{port}"
@property
def w3_ws_endpoint(self, i=0):
port = ports.evmrpc_ws_port(self.base_port(i))
return f"ws://localhost:{port}"
@property
def w3(self, i=0):
if self._w3 is None:
if self._use_websockets:
self._w3 = web3.Web3(
web3.providers.WebsocketProvider(self.w3_ws_endpoint)
)
else:
self._w3 = web3.Web3(web3.providers.HTTPProvider(self.w3_http_endpoint))
return self._w3
def base_port(self, i):
return self.config["validators"][i]["base_port"]
def node_rpc(self, i):
return "tcp://127.0.0.1:%d" % ports.rpc_port(self.base_port(i))
def use_websocket(self, use=True):
self._w3 = None
self._use_websockets = use
def cosmos_cli(self, i=0):
return CosmosCLI(
self.base_dir / f"node{i}", self.node_rpc(i), self.chain_binary
)
class Geth:
def __init__(self, w3):
self.w3 = w3
def setup_ethermint(path, base_port):
cfg = Path(__file__).parent / "configs/default.jsonnet"
yield from setup_custom_ethermint(path, base_port, cfg)
def setup_geth(path, base_port):
with (path / "geth.log").open("w") as logfile:
cmd = [
"start-geth",
path,
"--http.port",
str(base_port),
"--port",
str(base_port + 1),
]
print(*cmd)
proc = subprocess.Popen(
cmd,
preexec_fn=os.setsid,
stdout=logfile,
stderr=subprocess.STDOUT,
)
try:
wait_for_port(base_port)
w3 = web3.Web3(web3.providers.HTTPProvider(f"http://127.0.0.1:{base_port}"))
w3.middleware_onion.inject(geth_poa_middleware, layer=0)
yield Geth(w3)
finally:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
# proc.terminate()
proc.wait()
def setup_custom_ethermint(
path, base_port, config, post_init=None, chain_binary=None, wait_port=True
):
cmd = [
"pystarport",
"init",
"--config",
config,
"--data",
path,
"--base_port",
str(base_port),
"--no_remove",
]
if chain_binary is not None:
cmd = cmd[:1] + ["--cmd", chain_binary] + cmd[1:]
print(*cmd)
subprocess.run(cmd, check=True)
if post_init is not None:
post_init(path, base_port, config)
proc = subprocess.Popen(
["pystarport", "start", "--data", path, "--quiet"],
preexec_fn=os.setsid,
)
try:
if wait_port:
wait_for_port(ports.evmrpc_port(base_port))
wait_for_port(ports.evmrpc_ws_port(base_port))
yield Ethermint(
path / "ethermint_9000-1", chain_binary=chain_binary or DEFAULT_CHAIN_BINARY
)
finally:
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
proc.wait()
+1904
View File
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
[tool.poetry]
name = "integration_tests"
version = "0.1.0"
description = ""
authors = ["chain-dev <chain@crypto.com>"]
[tool.poetry.dependencies]
python = "^3.8"
pytest = "^7.0.1"
pytest-github-actions-annotate-failures = "^0.1.1"
flake8 = "^4.0.1"
black = "^22.3.0"
flake8-black = "^0.3.2"
flake8-isort = "^4.1.1"
pep8-naming = "^0.11.1"
protobuf = "^3.20.2"
grpcio = "^1.33.2"
PyYAML = "^5.3.1"
python-dateutil = "^2.8.1"
web3 = "^5.20.1"
eth-bloom = "^1.0.4"
python-dotenv = "^0.19.2"
pystarport = { git = "https://github.com/crypto-com/pystarport.git", branch = "main" }
websockets = "^9.1"
toml = "^0.10.2"
pysha3 = "^1.0.2"
jsonnet = "^0.18.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
+15
View File
@@ -0,0 +1,15 @@
{ system ? builtins.currentSystem, pkgs ? import ../../nix { inherit system; } }:
pkgs.mkShell {
buildInputs = [
pkgs.jq
(pkgs.callPackage ../../. { }) # ethermintd
pkgs.start-scripts
pkgs.go-ethereum
pkgs.cosmovisor
pkgs.nodejs
pkgs.test-env
];
shellHook = ''
. ${../../scripts/.env}
'';
}
+3
View File
@@ -0,0 +1,3 @@
def test_basic(cluster):
w3 = cluster.w3
assert w3.eth.chain_id == 9000
+118
View File
@@ -0,0 +1,118 @@
import pytest
from web3 import Web3
from .utils import (
ADDRS,
CONTRACTS,
deploy_contract,
send_successful_transaction,
send_transaction,
)
def test_pending_transaction_filter(cluster):
w3: Web3 = cluster.w3
flt = w3.eth.filter("pending")
# without tx
assert flt.get_new_entries() == [] # GetFilterChanges
# with tx
txhash = send_successful_transaction(w3)
assert txhash in flt.get_new_entries()
# without new txs since last call
assert flt.get_new_entries() == []
def test_block_filter(cluster):
w3: Web3 = cluster.w3
flt = w3.eth.filter("latest")
# without tx
assert flt.get_new_entries() == []
# with tx
send_successful_transaction(w3)
blocks = flt.get_new_entries()
assert len(blocks) >= 1
# without new txs since last call
assert flt.get_new_entries() == []
def test_event_log_filter_by_contract(cluster):
w3: Web3 = cluster.w3
contract = deploy_contract(w3, CONTRACTS["Greeter"])
assert contract.caller.greet() == "Hello"
# Create new filter from contract
current_height = hex(w3.eth.get_block_number())
flt = contract.events.ChangeGreeting.createFilter(fromBlock=current_height)
# without tx
assert flt.get_new_entries() == [] # GetFilterChanges
assert flt.get_all_entries() == [] # GetFilterLogs
# with tx
tx = contract.functions.setGreeting("world").buildTransaction()
tx_receipt = send_transaction(w3, tx)
assert tx_receipt.status == 1
log = contract.events.ChangeGreeting().processReceipt(tx_receipt)[0]
assert log["event"] == "ChangeGreeting"
new_entries = flt.get_new_entries()
assert len(new_entries) == 1
assert new_entries[0] == log
assert contract.caller.greet() == "world"
# without new txs since last call
assert flt.get_new_entries() == []
assert flt.get_all_entries() == new_entries
# Uninstall
assert w3.eth.uninstall_filter(flt.filter_id)
assert not w3.eth.uninstall_filter(flt.filter_id)
with pytest.raises(Exception):
flt.get_all_entries()
def test_event_log_filter_by_address(cluster):
w3: Web3 = cluster.w3
contract = deploy_contract(w3, CONTRACTS["Greeter"])
assert contract.caller.greet() == "Hello"
flt = w3.eth.filter({"address": contract.address})
flt2 = w3.eth.filter({"address": ADDRS["validator"]})
# without tx
assert flt.get_new_entries() == [] # GetFilterChanges
assert flt.get_all_entries() == [] # GetFilterLogs
# with tx
tx = contract.functions.setGreeting("world").buildTransaction()
receipt = send_transaction(w3, tx)
assert receipt.status == 1
assert len(flt.get_new_entries()) == 1
assert len(flt2.get_new_entries()) == 0
def test_get_logs(cluster):
w3: Web3 = cluster.w3
contract = deploy_contract(w3, CONTRACTS["Greeter"])
# without tx
assert w3.eth.get_logs({"address": contract.address}) == []
assert w3.eth.get_logs({"address": ADDRS["validator"]}) == []
# with tx
tx = contract.functions.setGreeting("world").buildTransaction()
receipt = send_transaction(w3, tx)
assert receipt.status == 1
assert len(w3.eth.get_logs({"address": contract.address})) == 1
assert len(w3.eth.get_logs({"address": ADDRS["validator"]})) == 0
+192
View File
@@ -0,0 +1,192 @@
import sys
from .network import Ethermint
from .utils import ADDRS, KEYS, eth_to_bech32, sign_transaction, wait_for_new_blocks
PRIORITY_REDUCTION = 1000000
def effective_gas_price(tx, base_fee):
if "maxFeePerGas" in tx:
# dynamic fee tx
return min(base_fee + tx["maxPriorityFeePerGas"], tx["maxFeePerGas"])
else:
# legacy tx
return tx["gasPrice"]
def tx_priority(tx, base_fee):
if "maxFeePerGas" in tx:
# dynamic fee tx
return (
min(tx["maxPriorityFeePerGas"], tx["maxFeePerGas"] - base_fee)
// PRIORITY_REDUCTION
)
else:
# legacy tx
return (tx["gasPrice"] - base_fee) // PRIORITY_REDUCTION
def test_priority(ethermint: Ethermint):
"""
test priorities of different tx types
use a relatively large priority number to counter
the effect of base fee change during the testing.
"""
w3 = ethermint.w3
amount = 10000
base_fee = w3.eth.get_block("latest").baseFeePerGas
# [ ( sender, tx ), ... ]
# use different senders to avoid nonce conflicts
test_cases = [
(
"validator",
{
"to": "0x0000000000000000000000000000000000000000",
"value": amount,
"gas": 21000,
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
"maxPriorityFeePerGas": 0,
},
),
(
"community",
{
"to": "0x0000000000000000000000000000000000000000",
"value": amount,
"gas": 21000,
"gasPrice": base_fee + PRIORITY_REDUCTION * 200000,
},
),
(
"signer2",
{
"to": "0x0000000000000000000000000000000000000000",
"value": amount,
"gasPrice": base_fee + PRIORITY_REDUCTION * 400000,
"accessList": [
{
"address": "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
"storageKeys": (
"0x00000000000000000000000000000000000000000000000000000000"
"00000003",
"0x00000000000000000000000000000000000000000000000000000000"
"00000007",
),
}
],
},
),
(
"signer1",
{
"to": "0x0000000000000000000000000000000000000000",
"value": amount,
"gas": 21000,
"maxFeePerGas": base_fee + PRIORITY_REDUCTION * 600000,
"maxPriorityFeePerGas": PRIORITY_REDUCTION * 600000,
},
),
]
# test cases are ordered by priority
expect_priorities = [tx_priority(tx, base_fee) for _, tx in test_cases]
assert expect_priorities == [0, 200000, 400000, 600000]
signed = [sign_transaction(w3, tx, key=KEYS[sender]) for sender, tx in test_cases]
# send the txs from low priority to high,
# but the later sent txs should be included earlier.
txhashes = [w3.eth.send_raw_transaction(tx.rawTransaction) for tx in signed]
receipts = [w3.eth.wait_for_transaction_receipt(txhash) for txhash in txhashes]
print(receipts)
assert all(receipt.status == 1 for receipt in receipts), "expect all txs success"
# the later txs should be included earlier because of higher priority
# FIXME there's some non-deterministics due to mempool logic
tx_indexes = [(r.blockNumber, r.transactionIndex) for r in receipts]
print(tx_indexes)
# the first sent tx are included later, because of lower priority
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
def test_native_tx_priority(ethermint: Ethermint):
cli = ethermint.cosmos_cli()
base_fee = cli.query_base_fee()
print("base_fee", base_fee)
test_cases = [
{
"from": eth_to_bech32(ADDRS["community"]),
"to": eth_to_bech32(ADDRS["validator"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
"max_priority_price": 0,
},
{
"from": eth_to_bech32(ADDRS["signer1"]),
"to": eth_to_bech32(ADDRS["signer2"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
"max_priority_price": PRIORITY_REDUCTION * 200000,
},
{
"from": eth_to_bech32(ADDRS["signer2"]),
"to": eth_to_bech32(ADDRS["signer1"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 400000}aphoton",
"max_priority_price": PRIORITY_REDUCTION * 400000,
},
{
"from": eth_to_bech32(ADDRS["validator"]),
"to": eth_to_bech32(ADDRS["community"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 600000}aphoton",
"max_priority_price": None, # no extension, maximum tipFeeCap
},
]
txs = []
expect_priorities = []
for tc in test_cases:
tx = cli.transfer(
tc["from"],
tc["to"],
tc["amount"],
gas_prices=tc["gas_prices"],
generate_only=True,
)
txs.append(
cli.sign_tx_json(
tx, tc["from"], max_priority_price=tc.get("max_priority_price")
)
)
gas_price = int(tc["gas_prices"].removesuffix("aphoton"))
expect_priorities.append(
min(
get_max_priority_price(tc.get("max_priority_price")),
gas_price - base_fee,
)
// PRIORITY_REDUCTION
)
assert expect_priorities == [0, 200000, 400000, 600000]
txhashes = []
for tx in txs:
rsp = cli.broadcast_tx_json(tx, broadcast_mode="sync")
assert rsp["code"] == 0, rsp["raw_log"]
txhashes.append(rsp["txhash"])
print("wait for two new blocks, so the sent txs are all included")
wait_for_new_blocks(cli, 2)
tx_results = [cli.tx_search_rpc(f"tx.hash='{txhash}'")[0] for txhash in txhashes]
tx_indexes = [(int(r["height"]), r["index"]) for r in tx_results]
print(tx_indexes)
# the first sent tx are included later, because of lower priority
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
def get_max_priority_price(max_priority_price):
"default to max int64 if None"
return max_priority_price if max_priority_price is not None else sys.maxsize
+142
View File
@@ -0,0 +1,142 @@
from pathlib import Path
import pytest
from eth_bloom import BloomFilter
from eth_utils import abi, big_endian_to_int
from hexbytes import HexBytes
from web3.datastructures import AttributeDict
from .network import setup_custom_ethermint
from .utils import (
ADDRS,
CONTRACTS,
KEYS,
deploy_contract,
sign_transaction,
w3_wait_for_new_blocks,
)
@pytest.fixture(scope="module")
def pruned(request, tmp_path_factory):
"""start-cronos
params: enable_auto_deployment
"""
yield from setup_custom_ethermint(
tmp_path_factory.mktemp("pruned"),
26900,
Path(__file__).parent / "configs/pruned_node.jsonnet",
)
def test_pruned_node(pruned):
"""
test basic json-rpc apis works in pruned node
"""
w3 = pruned.w3
erc20 = deploy_contract(
w3,
CONTRACTS["TestERC20A"],
key=KEYS["validator"],
)
tx = erc20.functions.transfer(ADDRS["community"], 10).buildTransaction(
{"from": ADDRS["validator"]}
)
signed = sign_transaction(w3, tx, KEYS["validator"])
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
print("wait for prunning happens")
w3_wait_for_new_blocks(w3, 10)
tx_receipt = w3.eth.get_transaction_receipt(txhash.hex())
assert len(tx_receipt.logs) == 1
expect_log = {
"address": erc20.address,
"topics": [
HexBytes(
abi.event_signature_to_log_topic("Transfer(address,address,uint256)")
),
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["validator"])),
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["community"])),
],
"data": "0x000000000000000000000000000000000000000000000000000000000000000a",
"transactionIndex": 0,
"logIndex": 0,
"removed": False,
}
assert expect_log.items() <= tx_receipt.logs[0].items()
# check get_balance and eth_call don't work on pruned state
# we need to check error message here.
# `get_balance` returns unmarshallJson and thats not what it should
res = w3.eth.get_balance(ADDRS["validator"], "latest")
assert res > 0
pruned_res = pruned.w3.provider.make_request(
"eth_getBalance", [ADDRS["validator"], hex(tx_receipt.blockNumber)]
)
assert "error" in pruned_res
assert (
pruned_res["error"]["message"] == "couldn't fetch balance. Node state is pruned"
)
with pytest.raises(Exception):
erc20.caller(block_identifier=tx_receipt.blockNumber).balanceOf(
ADDRS["validator"]
)
# check block bloom
block = w3.eth.get_block(tx_receipt.blockNumber)
assert "baseFeePerGas" in block
assert block.miner == "0x0000000000000000000000000000000000000000"
bloom = BloomFilter(big_endian_to_int(block.logsBloom))
assert HexBytes(erc20.address) in bloom
for topic in expect_log["topics"]:
assert topic in bloom
tx1 = w3.eth.get_transaction(txhash)
tx2 = w3.eth.get_transaction_by_block(
tx_receipt.blockNumber, tx_receipt.transactionIndex
)
exp_tx = AttributeDict(
{
"from": "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2",
"gas": 51542,
"input": (
"0xa9059cbb000000000000000000000000378c50d9264c63f3f92b806d4ee56e"
"9d86ffb3ec000000000000000000000000000000000000000000000000000000"
"000000000a"
),
"nonce": 2,
"to": erc20.address,
"transactionIndex": 0,
"value": 0,
"type": "0x2",
"accessList": [],
"chainId": "0x2328",
}
)
assert tx1 == tx2
for name in exp_tx.keys():
assert tx1[name] == tx2[name] == exp_tx[name]
logs = w3.eth.get_logs(
{
"fromBlock": tx_receipt.blockNumber,
"toBlock": tx_receipt.blockNumber,
}
)[0]
assert (
"address" in logs
and logs["address"] == "0x68542BD12B41F5D51D6282Ec7D91D7d0D78E4503"
)
assert "topics" in logs and len(logs["topics"]) == 3
assert logs["topics"][0] == HexBytes(
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
)
assert logs["topics"][1] == HexBytes(
"0x00000000000000000000000057f96e6b86cdefdb3d412547816a82e3e0ebf9d2"
)
assert logs["topics"][2] == HexBytes(
"0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"
)
+98
View File
@@ -0,0 +1,98 @@
import configparser
import subprocess
from pathlib import Path
import pytest
from pystarport import ports
from pystarport.cluster import SUPERVISOR_CONFIG_FILE
from .network import setup_custom_ethermint
from .utils import supervisorctl, wait_for_block, wait_for_port
def update_node2_cmd(path, cmd, i):
ini_path = path / SUPERVISOR_CONFIG_FILE
ini = configparser.RawConfigParser()
ini.read(ini_path)
for section in ini.sections():
if section == f"program:ethermint_9000-1-node{i}":
ini[section].update(
{
"command": f"{cmd} start --home %(here)s/node{i}",
"autorestart": "false", # don't restart when stopped
}
)
with ini_path.open("w") as fp:
ini.write(fp)
def post_init(broken_binary):
def inner(path, base_port, config):
chain_id = "ethermint_9000-1"
update_node2_cmd(path / chain_id, broken_binary, 2)
return inner
@pytest.fixture(scope="module")
def custom_ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("rollback")
cmd = [
"nix-build",
"--no-out-link",
Path(__file__).parent / "configs/broken-ethermintd.nix",
]
print(*cmd)
broken_binary = (
Path(subprocess.check_output(cmd).strip().decode()) / "bin/ethermintd"
)
print(broken_binary)
# init with genesis binary
yield from setup_custom_ethermint(
path,
26300,
Path(__file__).parent / "configs/rollback-test.jsonnet",
post_init=post_init(broken_binary),
wait_port=False,
)
def test_rollback(custom_ethermint):
"""
test using rollback command to fix app-hash mismatch situation.
- the broken node will sync up to block 10 then crash.
- use rollback command to rollback the db.
- switch to correct binary should make the node syncing again.
"""
wait_for_port(ports.rpc_port(custom_ethermint.base_port(2)))
print("wait for node2 to sync the first 10 blocks")
cli2 = custom_ethermint.cosmos_cli(2)
wait_for_block(cli2, 10)
print("wait for a few more blocks on the healthy nodes")
cli = custom_ethermint.cosmos_cli(0)
wait_for_block(cli, 13)
# (app hash mismatch happens after the 10th block, detected in the 11th block)
print("check node2 get stuck at block 10")
assert cli2.block_height() == 10
print("stop node2")
supervisorctl(
custom_ethermint.base_dir / "../tasks.ini", "stop", "ethermint_9000-1-node2"
)
print("do rollback on node2")
cli2.rollback()
print("switch to normal binary")
update_node2_cmd(custom_ethermint.base_dir, "ethermintd", 2)
supervisorctl(custom_ethermint.base_dir / "../tasks.ini", "update")
wait_for_port(ports.rpc_port(custom_ethermint.base_port(2)))
print("check node2 sync again")
cli2 = custom_ethermint.cosmos_cli(2)
wait_for_block(cli2, 15)
+370
View File
@@ -0,0 +1,370 @@
from .expected_constants import (
EXPECTED_FEE_HISTORY,
EXPECTED_GET_PROOF,
EXPECTED_GET_STORAGE_AT,
EXPECTED_GET_TRANSACTION,
EXPECTED_GET_TRANSACTION_RECEIPT,
)
from .utils import (
ADDRS,
CONTRACTS,
KEYS,
deploy_contract,
send_transaction,
w3_wait_for_block,
w3_wait_for_new_blocks,
)
def test_block(ethermint, geth):
get_blocks(ethermint, geth, False)
get_blocks(ethermint, geth, True)
def get_blocks(ethermint, geth, with_transactions):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc, geth_rpc, "eth_getBlockByNumber", ["0x0", with_transactions]
)
make_same_rpc_calls(
eth_rpc, geth_rpc, "eth_getBlockByNumber", ["0x2710", with_transactions]
)
ethermint_blk = ethermint.w3.eth.get_block(1)
# Get existing block, no transactions
eth_rsp = eth_rpc.make_request(
"eth_getBlockByHash", [ethermint_blk["hash"].hex(), with_transactions]
)
geth_rsp = geth_rpc.make_request(
"eth_getBlockByHash",
[
"0x124d099a1f435d3a6155e5d157ff1078eaefb742435892677ee5b3cb5e6fa055",
with_transactions,
],
)
res, err = same_types(eth_rsp, geth_rsp)
assert res, err
# Get not existing block
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBlockByHash",
[
"0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd",
with_transactions,
],
)
# Bad call
make_same_rpc_calls(
eth_rpc, geth_rpc, "eth_getBlockByHash", ["0", with_transactions]
)
def test_accounts(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_accounts", [])
def test_syncing(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_syncing", [])
def test_coinbase(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_coinbase", [])
def test_max_priority_fee(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_maxPriorityFeePerGas", [])
def test_gas_price(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_gasPrice", [])
def test_block_number(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_blockNumber", [])
def test_balance(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "latest"],
)
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "latest"])
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "latest"],
)
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x0"],
)
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "0x0"])
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "0x0"],
)
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x10000"],
)
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_getBalance", ["0", "0x10000"])
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBalance",
["0x9907a0cf64ec9fbf6ed8fd4971090de88222a9ac", "0x10000"],
)
def deploy_and_wait(w3, number=1):
contract = deploy_contract(
w3,
CONTRACTS["TestERC20A"],
)
w3_wait_for_new_blocks(w3, number)
return contract
def test_get_storage_at(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getStorageAt",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "0x0", "latest"],
)
contract = deploy_and_wait(ethermint.w3)
res = eth_rpc.make_request("eth_getStorageAt", [contract.address, "0x0", "latest"])
res, err = same_types(res["result"], EXPECTED_GET_STORAGE_AT)
assert res, err
def send_and_get_hash(w3, tx_value=10):
# Do an ethereum transfer
gas_price = w3.eth.gas_price
tx = {"to": ADDRS["community"], "value": tx_value, "gasPrice": gas_price}
return send_transaction(w3, tx, KEYS["validator"])["transactionHash"].hex()
def test_get_proof(ethermint, geth):
# on ethermint the proof query will fail for block numbers <= 2
# so we must wait for several blocks
w3_wait_for_block(ethermint.w3, 3)
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getProof",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", ["0x0"], "latest"],
)
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getProof",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", ["0x0"], "0x1024"],
)
_ = send_and_get_hash(ethermint.w3)
proof = eth_rpc.make_request(
"eth_getProof", [ADDRS["validator"], ["0x0"], "latest"]
)
res, err = same_types(proof["result"], EXPECTED_GET_PROOF)
assert res, err
def test_get_code(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getCode",
["0x57f96e6b86cdefdb3d412547816a82e3e0ebf9d2", "latest"],
)
# Do an ethereum transfer
contract = deploy_and_wait(ethermint.w3)
code = eth_rpc.make_request("eth_getCode", [contract.address, "latest"])
expected = {"id": "4", "jsonrpc": "2.0", "result": "0x"}
res, err = same_types(code, expected)
assert res, err
def test_get_block_transaction_count(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc, geth_rpc, "eth_getBlockTransactionCountByNumber", ["0x0"]
)
make_same_rpc_calls(
eth_rpc, geth_rpc, "eth_getBlockTransactionCountByNumber", ["0x100"]
)
tx_hash = send_and_get_hash(ethermint.w3)
tx_res = eth_rpc.make_request("eth_getTransactionByHash", [tx_hash])
block_number = tx_res["result"]["blockNumber"]
block_hash = tx_res["result"]["blockHash"]
block_res = eth_rpc.make_request(
"eth_getBlockTransactionCountByNumber", [block_number]
)
expected = {"id": "1", "jsonrpc": "2.0", "result": "0x1"}
res, err = same_types(block_res, expected)
assert res, err
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getBlockTransactionCountByHash",
["0x4e3a3754410177e6937ef1f84bba68ea139e8d1a2258c5f85db9f1cd715a1bdd"],
)
block_res = eth_rpc.make_request("eth_getBlockTransactionCountByHash", [block_hash])
expected = {"id": "1", "jsonrpc": "2.0", "result": "0x1"}
res, err = same_types(block_res, expected)
assert res, err
def test_get_transaction(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getTransactionByHash",
["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"],
)
tx_hash = send_and_get_hash(ethermint.w3)
tx_res = eth_rpc.make_request("eth_getTransactionByHash", [tx_hash])
res, err = same_types(tx_res, EXPECTED_GET_TRANSACTION)
assert res, err
def test_get_transaction_receipt(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(
eth_rpc,
geth_rpc,
"eth_getTransactionReceipt",
["0x5c504ed432cb51138bcf09aa5e8a410dd4a1e204ef84bfed1be16dfba1b22060"],
)
tx_hash = send_and_get_hash(ethermint.w3)
tx_res = eth_rpc.make_request("eth_getTransactionReceipt", [tx_hash])
res, err = same_types(tx_res["result"], EXPECTED_GET_TRANSACTION_RECEIPT)
assert res, err
def test_fee_history(ethermint, geth):
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_feeHistory", [4, "latest", [10, 90]])
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_feeHistory", [4, "0x5000", [10, 90]])
fee_history = eth_rpc.make_request("eth_feeHistory", [4, "latest", [10, 90]])
res, err = same_types(fee_history["result"], EXPECTED_FEE_HISTORY)
assert res, err
def test_estimate_gas(ethermint, geth):
tx = {"to": ADDRS["community"], "from": ADDRS["validator"]}
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx])
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx, "0x0"])
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [tx, "0x5000"])
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_estimateGas", [{}])
def make_same_rpc_calls(rpc1, rpc2, method, params):
res1 = rpc1.make_request(method, params)
res2 = rpc2.make_request(method, params)
res, err = same_types(res1, res2)
assert res, err
def test_incomplete_send_transaction(ethermint, geth):
# Send ethereum tx with nothing in from field
eth_rpc = ethermint.w3.provider
geth_rpc = geth.w3.provider
gas_price = ethermint.w3.eth.gas_price
tx = {"from": "", "to": ADDRS["community"], "value": 0, "gasPrice": gas_price}
make_same_rpc_calls(eth_rpc, geth_rpc, "eth_sendTransaction", [tx])
def same_types(object_a, object_b):
if isinstance(object_a, dict):
if not isinstance(object_b, dict):
return False, "A is dict, B is not"
keys = list(set(list(object_a.keys()) + list(object_b.keys())))
for key in keys:
if key in object_b and key in object_a:
if not same_types(object_a[key], object_b[key]):
return False, key + " key on dict are not of same type"
else:
return False, key + " key on json is not in both results"
return True, ""
elif isinstance(object_a, list):
if not isinstance(object_b, list):
return False, "A is list, B is not"
if len(object_a) == 0 and len(object_b) == 0:
return True, ""
if len(object_a) > 0 and len(object_b) > 0:
return same_types(object_a[0], object_b[0])
else:
return True
elif object_a is None and object_b is None:
return True, ""
elif type(object_a) is type(object_b):
return True, ""
else:
return (
False,
"different types. A is type "
+ type(object_a).__name__
+ " B is type "
+ type(object_b).__name__,
)
+173
View File
@@ -0,0 +1,173 @@
import configparser
import json
import re
import subprocess
from pathlib import Path
import pytest
from dateutil.parser import isoparse
from pystarport import ports
from pystarport.cluster import SUPERVISOR_CONFIG_FILE
from .network import Ethermint, setup_custom_ethermint
from .utils import (
ADDRS,
CONTRACTS,
deploy_contract,
parse_events,
send_transaction,
wait_for_block,
wait_for_block_time,
wait_for_port,
)
def init_cosmovisor(home):
"""
build and setup cosmovisor directory structure in each node's home directory
"""
cosmovisor = home / "cosmovisor"
cosmovisor.mkdir()
(cosmovisor / "upgrades").symlink_to("../../../upgrades")
(cosmovisor / "genesis").symlink_to("./upgrades/genesis")
def post_init(path, base_port, config):
"""
prepare cosmovisor for each node
"""
chain_id = "ethermint_9000-1"
cfg = json.loads((path / chain_id / "config.json").read_text())
for i, _ in enumerate(cfg["validators"]):
home = path / chain_id / f"node{i}"
init_cosmovisor(home)
# patch supervisord ini config
ini_path = path / chain_id / SUPERVISOR_CONFIG_FILE
ini = configparser.RawConfigParser()
ini.read(ini_path)
reg = re.compile(rf"^program:{chain_id}-node(\d+)")
for section in ini.sections():
m = reg.match(section)
if m:
i = m.group(1)
ini[section].update(
{
"command": f"cosmovisor start --home %(here)s/node{i}",
"environment": (
f"DAEMON_NAME=ethermintd,DAEMON_HOME=%(here)s/node{i}"
),
}
)
with ini_path.open("w") as fp:
ini.write(fp)
@pytest.fixture(scope="module")
def custom_ethermint(tmp_path_factory):
path = tmp_path_factory.mktemp("upgrade")
cmd = [
"nix-build",
Path(__file__).parent / "configs/upgrade-test-package.nix",
"-o",
path / "upgrades",
]
print(*cmd)
subprocess.run(cmd, check=True)
# init with genesis binary
yield from setup_custom_ethermint(
path,
26100,
Path(__file__).parent / "configs/cosmovisor.jsonnet",
post_init=post_init,
chain_binary=str(path / "upgrades/genesis/bin/ethermintd"),
)
def test_cosmovisor_upgrade(custom_ethermint: Ethermint):
"""
- propose an upgrade and pass it
- wait for it to happen
- it should work transparently
- check that queries on legacy blocks still works after upgrade.
"""
cli = custom_ethermint.cosmos_cli()
height = cli.block_height()
target_height = height + 5
print("upgrade height", target_height)
w3 = custom_ethermint.w3
contract = deploy_contract(w3, CONTRACTS["TestERC20A"])
old_height = w3.eth.block_number
old_balance = w3.eth.get_balance(ADDRS["validator"], block_identifier=old_height)
old_base_fee = w3.eth.get_block(old_height).baseFeePerGas
old_erc20_balance = contract.caller.balanceOf(ADDRS["validator"])
print("old values", old_height, old_balance, old_base_fee)
plan_name = "integration-test-upgrade"
rsp = cli.gov_propose(
"community",
"software-upgrade",
{
"name": plan_name,
"title": "upgrade test",
"description": "ditto",
"upgrade-height": target_height,
"deposit": "10000aphoton",
},
)
assert rsp["code"] == 0, rsp["raw_log"]
# get proposal_id
ev = parse_events(rsp["logs"])["submit_proposal"]
assert ev["proposal_type"] == "SoftwareUpgrade", rsp
proposal_id = ev["proposal_id"]
rsp = cli.gov_vote("validator", proposal_id, "yes")
assert rsp["code"] == 0, rsp["raw_log"]
# rsp = custom_ethermint.cosmos_cli(1).gov_vote("validator", proposal_id, "yes")
# assert rsp["code"] == 0, rsp["raw_log"]
proposal = cli.query_proposal(proposal_id)
wait_for_block_time(cli, isoparse(proposal["voting_end_time"]))
proposal = cli.query_proposal(proposal_id)
assert proposal["status"] == "PROPOSAL_STATUS_PASSED", proposal
# update cli chain binary
custom_ethermint.chain_binary = (
Path(custom_ethermint.chain_binary).parent.parent.parent
/ f"{plan_name}/bin/ethermintd"
)
cli = custom_ethermint.cosmos_cli()
# block should pass the target height
wait_for_block(cli, target_height + 1, timeout=480)
wait_for_port(ports.rpc_port(custom_ethermint.base_port(0)))
# test migrate keystore
cli.migrate_keystore()
# check basic tx works after upgrade
wait_for_port(ports.evmrpc_port(custom_ethermint.base_port(0)))
receipt = send_transaction(
w3,
{
"to": ADDRS["community"],
"value": 1000,
"maxFeePerGas": 1000000000000,
"maxPriorityFeePerGas": 10000,
},
)
assert receipt.status == 1
# check json-rpc query on older blocks works
assert old_balance == w3.eth.get_balance(
ADDRS["validator"], block_identifier=old_height
)
assert old_base_fee == w3.eth.get_block(old_height).baseFeePerGas
# check eth_call on older blocks works
assert old_erc20_balance == contract.caller(
block_identifier=target_height - 2
).balanceOf(ADDRS["validator"])
+180
View File
@@ -0,0 +1,180 @@
import json
import os
import socket
import subprocess
import sys
import time
from pathlib import Path
import bech32
from dateutil.parser import isoparse
from dotenv import load_dotenv
from eth_account import Account
from hexbytes import HexBytes
from web3._utils.transactions import fill_nonce, fill_transaction_defaults
load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env")
Account.enable_unaudited_hdwallet_features()
ACCOUNTS = {
"validator": Account.from_mnemonic(os.getenv("VALIDATOR1_MNEMONIC")),
"community": Account.from_mnemonic(os.getenv("COMMUNITY_MNEMONIC")),
"signer1": Account.from_mnemonic(os.getenv("SIGNER1_MNEMONIC")),
"signer2": Account.from_mnemonic(os.getenv("SIGNER2_MNEMONIC")),
}
KEYS = {name: account.key for name, account in ACCOUNTS.items()}
ADDRS = {name: account.address for name, account in ACCOUNTS.items()}
ETHERMINT_ADDRESS_PREFIX = "ethm"
TEST_CONTRACTS = {
"TestERC20A": "TestERC20A.sol",
"Greeter": "Greeter.sol",
}
def contract_path(name, filename):
return (
Path(__file__).parent
/ "contracts/artifacts/contracts/"
/ filename
/ (name + ".json")
)
CONTRACTS = {
**{
name: contract_path(name, filename) for name, filename in TEST_CONTRACTS.items()
},
}
def wait_for_port(port, host="127.0.0.1", timeout=40.0):
start_time = time.perf_counter()
while True:
try:
with socket.create_connection((host, port), timeout=timeout):
break
except OSError as ex:
time.sleep(0.1)
if time.perf_counter() - start_time >= timeout:
raise TimeoutError(
"Waited too long for the port {} on host {} to start accepting "
"connections.".format(port, host)
) from ex
def w3_wait_for_new_blocks(w3, n):
begin_height = w3.eth.block_number
while True:
time.sleep(0.5)
cur_height = w3.eth.block_number
if cur_height - begin_height >= n:
break
def wait_for_new_blocks(cli, n):
begin_height = int((cli.status())["SyncInfo"]["latest_block_height"])
while True:
time.sleep(0.5)
cur_height = int((cli.status())["SyncInfo"]["latest_block_height"])
if cur_height - begin_height >= n:
break
def wait_for_block(cli, height, timeout=240):
for i in range(timeout * 2):
try:
status = cli.status()
except AssertionError as e:
print(f"get sync status failed: {e}", file=sys.stderr)
else:
current_height = int(status["SyncInfo"]["latest_block_height"])
if current_height >= height:
break
print("current block height", current_height)
time.sleep(0.5)
else:
raise TimeoutError(f"wait for block {height} timeout")
def w3_wait_for_block(w3, height, timeout=240):
for i in range(timeout * 2):
try:
current_height = w3.eth.block_number
except Exception as e:
print(f"get json-rpc block number failed: {e}", file=sys.stderr)
else:
if current_height >= height:
break
print("current block height", current_height)
time.sleep(0.5)
else:
raise TimeoutError(f"wait for block {height} timeout")
def wait_for_block_time(cli, t):
print("wait for block time", t)
while True:
now = isoparse((cli.status())["SyncInfo"]["latest_block_time"])
print("block time now:", now)
if now >= t:
break
time.sleep(0.5)
def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]):
"""
deploy contract and return the deployed contract instance
"""
acct = Account.from_key(key)
info = json.loads(jsonfile.read_text())
contract = w3.eth.contract(abi=info["abi"], bytecode=info["bytecode"])
tx = contract.constructor(*args).buildTransaction({"from": acct.address})
txreceipt = send_transaction(w3, tx, key)
assert txreceipt.status == 1
address = txreceipt.contractAddress
return w3.eth.contract(address=address, abi=info["abi"])
def fill_defaults(w3, tx):
return fill_nonce(w3, fill_transaction_defaults(w3, tx))
def sign_transaction(w3, tx, key=KEYS["validator"]):
"fill default fields and sign"
acct = Account.from_key(key)
tx["from"] = acct.address
tx = fill_transaction_defaults(w3, tx)
tx = fill_nonce(w3, tx)
return acct.sign_transaction(tx)
def send_transaction(w3, tx, key=KEYS["validator"]):
signed = sign_transaction(w3, tx, key)
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
return w3.eth.wait_for_transaction_receipt(txhash)
def send_successful_transaction(w3):
signed = sign_transaction(w3, {"to": ADDRS["community"], "value": 1000})
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
receipt = w3.eth.wait_for_transaction_receipt(txhash)
assert receipt.status == 1
return txhash
def eth_to_bech32(addr, prefix=ETHERMINT_ADDRESS_PREFIX):
bz = bech32.convertbits(HexBytes(addr), 8, 5)
return bech32.bech32_encode(prefix, bz)
def supervisorctl(inipath, *args):
subprocess.run(
(sys.executable, "-msupervisor.supervisorctl", "-c", inipath, *args),
check=True,
)
def parse_events(logs):
return {
ev["type"]: {attr["key"]: attr["value"] for attr in ev["attributes"]}
for ev in logs[0]["events"]
}
+7 -7
View File
@@ -1,7 +1,5 @@
// This is a test utility for Ethermint's Web3 JSON-RPC services.
//
// To run these tests please first ensure you have the ethermintd running
//
// You can configure the desired HOST and MODE as well in integration-test-all.sh
package rpc
@@ -10,12 +8,13 @@ import (
"fmt"
"math/big"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/stretchr/testify/require"
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
rpctypes "github.com/cerc-io/laconicd/rpc/types"
)
// func TestMain(m *testing.M) {
@@ -269,8 +268,9 @@ func TestEth_Pending_GetTransactionByBlockNumberAndIndex(t *testing.T) {
}
func TestEth_Pending_GetTransactionByHash(t *testing.T) {
sleep := 0 * time.Second
// negative case, check that it returns empty.
rpcRes := Call(t, "eth_getTransactionByHash", []interface{}{"0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973"})
rpcRes := CallWithSleep(t, "eth_getTransactionByHash", []interface{}{"0xec5fa15e1368d6ac314f9f64118c5794f076f63c02e66f97ea5fe1de761a8973"}, sleep)
var tx map[string]interface{}
err := json.Unmarshal(rpcRes.Result, &tx)
require.NoError(t, err)
@@ -281,17 +281,17 @@ func TestEth_Pending_GetTransactionByHash(t *testing.T) {
param := makePendingTxParams(t)
param[0]["data"] = data
txRes := Call(t, "eth_sendTransaction", param)
txRes := CallWithSleep(t, "eth_sendTransaction", param, sleep)
var txHash common.Hash
err = txHash.UnmarshalJSON(txRes.Result)
require.NoError(t, err)
rpcRes = Call(t, "eth_getTransactionByHash", []interface{}{txHash})
rpcRes = CallWithSleep(t, "eth_getTransactionByHash", []interface{}{txHash}, sleep)
var pendingTx map[string]interface{}
err = json.Unmarshal(rpcRes.Result, &pendingTx)
require.NoError(t, err)
txsRes := Call(t, "eth_getPendingTransactions", []interface{}{})
txsRes := CallWithSleep(t, "eth_getPendingTransactions", []interface{}{}, sleep)
var pendingTxs []map[string]interface{}
err = json.Unmarshal(txsRes.Result, &pendingTxs)
require.NoError(t, err)
+31 -2
View File
@@ -17,7 +17,7 @@ import (
"github.com/stretchr/testify/require"
rpctypes "github.com/cerc-io/laconicd/rpc/ethereum/types"
rpctypes "github.com/cerc-io/laconicd/rpc/types"
ethermint "github.com/cerc-io/laconicd/types"
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
@@ -301,6 +301,17 @@ func deployTestContract(t *testing.T) (hexutil.Bytes, map[string]interface{}) {
return hash, receipt
}
func getTransactionReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
param := []string{hash.String()}
rpcRes := call(t, "eth_getTransactionReceipt", param)
receipt := make(map[string]interface{})
err := json.Unmarshal(rpcRes.Result, &receipt)
require.NoError(t, err)
return receipt
}
func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
timeout := time.After(12 * time.Second)
ticker := time.Tick(500 * time.Millisecond)
@@ -310,7 +321,7 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
case <-timeout:
return nil
case <-ticker:
receipt := GetTransactionReceipt(t, hash)
receipt := getTransactionReceipt(t, hash)
if receipt != nil {
return receipt
}
@@ -318,6 +329,24 @@ func waitForReceipt(t *testing.T, hash hexutil.Bytes) map[string]interface{} {
}
}
func TestEth_IncompleteSendTransaction(t *testing.T) {
// get gasprice
gasPrice := GetGasPrice(t)
// make tx params without from address
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
param[0]["from"] = ""
param[0]["to"] = "0x1122334455667788990011223344556677889900"
param[0]["value"] = "0x1"
param[0]["gasPrice"] = gasPrice
_, err := callWithError("eth_sendTransaction", param)
// require well-formatted error (should not be "method handler crashed")
require.Error(t, err)
require.NotEqual(t, err.Error(), "method handler crashed", "no from field dealt with incorrectly")
}
func TestEth_GetFilterChanges_NoTopics(t *testing.T) {
rpcRes := call(t, "eth_blockNumber", []string{})
+10 -4
View File
@@ -63,12 +63,14 @@ func CreateRequest(method string, params interface{}) Request {
}
}
func Call(t *testing.T, method string, params interface{}) *Response {
func CallWithSleep(t *testing.T, method string, params interface{}, sleep time.Duration) *Response {
req, err := json.Marshal(CreateRequest(method, params))
require.NoError(t, err)
var rpcRes *Response
time.Sleep(1 * time.Second)
if sleep > 0 {
time.Sleep(sleep)
}
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", HOST, bytes.NewBuffer(req))
if err != nil {
@@ -94,6 +96,10 @@ func Call(t *testing.T, method string, params interface{}) *Response {
return rpcRes
}
func Call(t *testing.T, method string, params interface{}) *Response {
return CallWithSleep(t, method, params, time.Second)
}
func CallWithError(method string, params interface{}) (*Response, error) {
req, err := json.Marshal(CreateRequest(method, params))
if err != nil {
@@ -167,7 +173,7 @@ func DeployTestContract(t *testing.T, addr []byte) (hexutil.Bytes, map[string]in
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
param[0]["from"] = "0x" + fmt.Sprintf("%x", addr)
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029"
param[0]["data"] = "0x6080604052348015600f57600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a2603580604b6000396000f3fe6080604052600080fdfea165627a7a723058206cab665f0f557620554bb45adf266708d2bd349b8a4314bdff205ee8440e3c240029" //nolint:lll
param[0]["gas"] = "0x200000"
rpcRes := Call(t, "personal_unlockAccount", []interface{}{param[0]["from"], ""})
@@ -206,7 +212,7 @@ func DeployTestContractWithFunction(t *testing.T, addr []byte) hexutil.Bytes {
// }
// }
bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032"
bytecode := "0x608060405234801561001057600080fd5b5060117f775a94827b8fd9b519d36cd827093c664f93347070a554f65e4a6f56cd73889860405160405180910390a260d08061004d6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063eb8ac92114602d575b600080fd5b606060048036036040811015604157600080fd5b8101908080359060200190929190803590602001909291905050506062565b005b8160008190555080827ff3ca124a697ba07e8c5e80bebcfcc48991fc16a63170e8a9206e30508960d00360405160405180910390a3505056fea265627a7a723158201d94d2187aaf3a6790527b615fcc40970febf0385fa6d72a2344848ebd0df3e964736f6c63430005110032" //nolint:lll
param := make([]map[string]string, 1)
param[0] = make(map[string]string)
+174
View File
@@ -0,0 +1,174 @@
package rpc
import (
"encoding/json"
"fmt"
"github.com/gorilla/websocket"
"github.com/stretchr/testify/require"
"net/url"
"os"
"strings"
"testing"
"time"
)
var (
HOST_WS = os.Getenv("HOST_WS")
wsUrl string
)
func init() {
if HOST_WS == "" {
HOST_WS = "localhost:8542"
}
u := url.URL{Scheme: "ws", Host: HOST_WS, Path: ""}
wsUrl = u.String()
}
func TestWsSingleRequest(t *testing.T) {
t.Log("test simple rpc request net_version with Websocket")
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
require.NoError(t, err)
defer wc.Close()
wsWriteMessage(t, wc, `{"jsonrpc":"2.0","method":"net_version","params":[],"id":1}`)
time.Sleep(1 * time.Second)
mb := readMessage(t, wc)
msg := jsonUnmarshal(t, mb)
result, ok := msg["result"].(string)
require.True(t, ok)
require.Equal(t, "9000", result)
}
func TestWsBatchRequest(t *testing.T) {
t.Log("test batch request with Websocket")
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
require.NoError(t, err)
defer wc.Close()
wsWriteMessage(t, wc, `[{"jsonrpc":"2.0","method":"net_version","params":[],"id":1},{"jsonrpc":"2.0","method":"eth_protocolVersion","params":[],"id":2}]`)
time.Sleep(1 * time.Second)
mb := readMessage(t, wc)
var msg []map[string]interface{}
err = json.Unmarshal(mb, &msg)
require.NoError(t, err)
require.Equal(t, 2, len(msg))
// net_version
resNetVersion := msg[0]
result, ok := resNetVersion["result"].(string)
require.True(t, ok)
require.Equal(t, "9000", result)
id, ok := resNetVersion["id"].(float64)
require.True(t, ok)
require.Equal(t, 1, int(id))
// eth_protocolVersion
resEthProtocolVersion := msg[1]
result, ok = resEthProtocolVersion["result"].(string)
require.True(t, ok)
require.Equal(t, "0x41", result)
id, ok = resEthProtocolVersion["id"].(float64)
require.True(t, ok)
require.Equal(t, 2, int(id))
}
func TestWsEth_subscribe_newHeads(t *testing.T) {
t.Log("test eth_subscribe newHeads with Websocket")
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
require.NoError(t, err)
defer wc.Close()
wsWriteMessage(t, wc, `{"id":1,"method":"eth_subscribe","params":["newHeads",{}]}`)
time.Sleep(1 * time.Second)
mb := readMessage(t, wc)
msg := jsonUnmarshal(t, mb)
subscribeId, ok := msg["result"].(string)
require.True(t, ok)
require.True(t, strings.HasPrefix(subscribeId, "0x"))
time.Sleep(3 * time.Second)
mb = readMessage(t, wc)
msg = jsonUnmarshal(t, mb)
method, ok := msg["method"].(string)
require.Equal(t, "eth_subscription", method)
// id should not exist with eth_subscription event
_, ok = msg["id"].(float64)
require.False(t, ok)
wsUnsubscribe(t, wc, subscribeId) // eth_unsubscribe
}
func TestWsEth_subscribe_log(t *testing.T) {
t.Log("test eth_subscribe log with websocket")
wc, _, err := websocket.DefaultDialer.Dial(wsUrl, nil)
require.NoError(t, err)
defer wc.Close()
wsWriteMessage(t, wc, fmt.Sprintf(`{"jsonrpc":"2.0","id":1,"method":"eth_subscribe","params":["logs",{"topics":["%s", "%s"]}]}`, helloTopic, worldTopic))
time.Sleep(1 * time.Second)
mb := readMessage(t, wc)
msg := jsonUnmarshal(t, mb)
subscribeId, ok := msg["result"].(string)
require.True(t, ok)
require.True(t, strings.HasPrefix(subscribeId, "0x"))
// do something here to receive subscription messages
deployTestContractWithFunction(t)
time.Sleep(3 * time.Second)
mb = readMessage(t, wc)
msg = jsonUnmarshal(t, mb)
method, ok := msg["method"].(string)
require.Equal(t, "eth_subscription", method)
// id should not exist with eth_subscription event
_, ok = msg["id"].(float64)
require.False(t, ok)
wsUnsubscribe(t, wc, subscribeId) // eth_unsubscribe
}
func wsWriteMessage(t *testing.T, wc *websocket.Conn, jsonStr string) {
t.Logf("send: %s", jsonStr)
err := wc.WriteMessage(websocket.TextMessage, []byte(jsonStr))
require.NoError(t, err)
}
func wsUnsubscribe(t *testing.T, wc *websocket.Conn, subscribeId string) {
t.Logf("eth_unsubscribe %s", subscribeId)
wsWriteMessage(t, wc, fmt.Sprintf(`{"id":1,"method":"eth_unsubscribe","params":["%s"]}`, subscribeId))
time.Sleep(1 * time.Second)
mb := readMessage(t, wc)
msg := jsonUnmarshal(t, mb)
result, ok := msg["result"].(bool)
require.True(t, ok)
require.True(t, result)
}
func readMessage(t *testing.T, wc *websocket.Conn) []byte {
_, mb, err := wc.ReadMessage()
require.NoError(t, err)
t.Logf("recv: %s", mb)
return mb
}
func jsonUnmarshal(t *testing.T, mb []byte) map[string]interface{} {
var msg map[string]interface{}
err := json.Unmarshal(mb, &msg)
require.NoError(t, err)
return msg
}
+5 -6
View File
@@ -16,7 +16,7 @@ Increasingly difficult tests are provided:
To run the tests, you can use the `test-helper.js` utility to test all suites under `ganache` or `ethermint` network. The `test-helper.js` will help you spawn an `ethermintd` process before running the tests.
You can simply run `yarn test --network ethermint` to run all tests with ethermint network, or you can run `yarn test --network ganache` to use ganache shipped with truffle. In most cases, there two networks should produce identical test results.
You can simply run `yarn test --network ethermint` to run all tests with ethermint network, or you can run `yarn test --network ganache` to use ganache shipped with truffle. In most cases, there two networks should produce identical test results.
If you only want to run a few test cases, append the name of tests following by the command line. For example, use `yarn test --network ethermint basic` to run the `basic` test under `ethermint` network.
@@ -32,13 +32,12 @@ You will now have three ethereum accounts unlocked in the test node:
- `0xddd64b4712f7c8f1ace3c145c950339eddaf221d` (User 1)
- `0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0` (user 2)
Keep the terminal window open, go into any of the tests and run `yarn test-ethermint`. You should see `ethermintd` accepting transactions and producing blocks. You should be able to query for any transaction via:
- `ethermintd query tx <cosmos-sdk tx>`
- `curl localhost:8545 -H "Content-Type:application/json" -X POST --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["<ethereum tx>"],"id":1}'`
From here, in your other available terminal,
From here, in your other available terminal,
And obviously more, via the Ethereum JSON-RPC API).
When in doubt, you can also run the tests against a Ganache instance via `yarn test-ganache`, to make sure they are behaving correctly.
@@ -48,11 +47,11 @@ When in doubt, you can also run the tests against a Ganache instance via `yarn t
The [`init-test-node.sh`](./init-test-node.sh) script sets up ethermint with the following accounts:
- `ethm10jmp6sgh4cc6zt3e8gw05wavvejgr5pwtu750w` (Validator)
- `0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E`
- `0x7cB61D4117AE31a12E393a1Cfa3BaC666481D02E`
- `ethm1cml96vmptgw99syqrrz8az79xer2pcgp767p9e` (User 1)
- `0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101`
- `0xC6Fe5D33615a1C52c08018c47E8Bc53646A0E101`
- `ethm1jcltmuhplrdcwp7stlr4hlhlhgd4htqhgjpff2` (user 2)
- `0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17`
- `0x963EBDf2e1f8DB8707D05FC75bfeFFBa1B5BaC17`
Each with roughly 100 ETH available (1e18 photon).
+18 -18
View File
@@ -24,38 +24,38 @@ USER4_KEY="user4"
USER4_MNEMONIC="doll midnight silk carpet brush boring pluck office gown inquiry duck chief aim exit gain never tennis crime fragile ship cloud surface exotic patch"
# remove existing daemon and client
rm -rf ~/.laconic*
rm -rf ~/.ethermint*
# Import keys from mnemonics
echo $VAL_MNEMONIC | laconicd keys add $VAL_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER1_MNEMONIC | laconicd keys add $USER1_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER2_MNEMONIC | laconicd keys add $USER2_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER3_MNEMONIC | laconicd keys add $USER3_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER4_MNEMONIC | laconicd keys add $USER4_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $VAL_MNEMONIC | ethermintd keys add $VAL_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER1_MNEMONIC | ethermintd keys add $USER1_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER2_MNEMONIC | ethermintd keys add $USER2_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER3_MNEMONIC | ethermintd keys add $USER3_KEY --recover --keyring-backend test --algo "eth_secp256k1"
echo $USER4_MNEMONIC | ethermintd keys add $USER4_KEY --recover --keyring-backend test --algo "eth_secp256k1"
laconicd init $MONIKER --chain-id $CHAINID
ethermintd init $MONIKER --chain-id $CHAINID
# Set gas limit in genesis
cat $HOME/.laconicd/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $HOME/.laconicd/config/tmp_genesis.json && mv $HOME/.laconicd/config/tmp_genesis.json $HOME/.laconicd/config/genesis.json
cat $HOME/.ethermintd/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="10000000"' > $HOME/.ethermintd/config/tmp_genesis.json && mv $HOME/.ethermintd/config/tmp_genesis.json $HOME/.ethermintd/config/genesis.json
# Reduce the block time to 1s
sed -i -e '/^timeout_commit =/ s/= .*/= "850ms"/' $HOME/.laconicd/config/config.toml
sed -i -e '/^timeout_commit =/ s/= .*/= "850ms"/' $HOME/.ethermintd/config/config.toml
# Allocate genesis accounts (cosmos formatted addresses)
laconicd add-genesis-account "$(laconicd keys show $VAL_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
laconicd add-genesis-account "$(laconicd keys show $USER1_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
laconicd add-genesis-account "$(laconicd keys show $USER2_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
laconicd add-genesis-account "$(laconicd keys show $USER3_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
laconicd add-genesis-account "$(laconicd keys show $USER4_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
ethermintd add-genesis-account "$(ethermintd keys show $VAL_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
ethermintd add-genesis-account "$(ethermintd keys show $USER1_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
ethermintd add-genesis-account "$(ethermintd keys show $USER2_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
ethermintd add-genesis-account "$(ethermintd keys show $USER3_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
ethermintd add-genesis-account "$(ethermintd keys show $USER4_KEY -a --keyring-backend test)" 1000000000000000000000aphoton,1000000000000000000stake --keyring-backend test
# Sign genesis transaction
laconicd gentx $VAL_KEY 1000000000000000000stake --amount=1000000000000000000000aphoton --chain-id $CHAINID --keyring-backend test
ethermintd gentx $VAL_KEY 1000000000000000000stake --amount=1000000000000000000000aphoton --chain-id $CHAINID --keyring-backend test
# Collect genesis tx
laconicd collect-gentxs
ethermintd collect-gentxs
# Run this to ensure everything worked and that the genesis file is setup correctly
laconicd validate-genesis
ethermintd validate-genesis
# Start the node (remove the --pruning=nothing flag if historical queries are not needed)
laconicd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable --mode validator
ethermintd start --pruning=nothing --rpc.unsafe --keyring-backend test --log_level info --json-rpc.api eth,txpool,personal,net,debug,web3 --api.enable
+1 -1
View File
@@ -13,7 +13,7 @@
]
},
"dependencies": {
"truffle": "5.4.14",
"truffle": "5.5.8",
"yargs": "^17.0.1",
"patch-package": "^6.4.7"
},
+1 -1
View File
@@ -11,6 +11,6 @@
"truffle-assertions": "^0.9.2"
},
"dependencies": {
"@truffle/hdwallet-provider": "^1.5.1-alpha.1"
"@truffle/hdwallet-provider": "^1.6.0"
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"@truffle/hdwallet-provider": "^1.4.1",
"@truffle/hdwallet-provider": "^1.6.0",
"concurrently": "^6.2.0",
"truffle-assertions": "^0.9.2"
},
+3 -3
View File
@@ -181,7 +181,7 @@ function setupNetwork({ runConfig, timeout }) {
stdio: ['ignore', runConfig.verboseLog ? 'pipe' : 'ignore', 'pipe'],
});
logger.info(`Starting Laconicd process... timeout: ${timeout}ms`);
logger.info(`Starting Ethermintd process... timeout: ${timeout}ms`);
if (runConfig.verboseLog) {
ethermintdProc.stdout.pipe(process.stdout);
}
@@ -191,14 +191,14 @@ function setupNetwork({ runConfig, timeout }) {
process.stdout.write(oLine);
}
if (oLine.indexOf('Starting JSON-RPC server') !== -1) {
logger.info('Laconicd started');
logger.info('Ethermintd started');
resolve(ethermintdProc);
}
});
});
const timeoutPromise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Start laconicd timeout!')), timeout);
setTimeout(() => reject(new Error('Start ethermintd timeout!')), timeout);
});
return Promise.race([spawnPromise, timeoutPromise]);
}
+1000 -139
View File
File diff suppressed because it is too large Load Diff