hive: proper rpc test

This commit is contained in:
jangko 2022-05-31 15:42:01 +07:00
parent b03c2dca4f
commit 0ad7ffcdbe
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
6 changed files with 345 additions and 175 deletions

View File

@ -40,3 +40,32 @@ proc balanceAt*(client: RpcClient, address: EthAddress, blockNumber: uint64): Fu
proc balanceAt*(client: RpcClient, address: EthAddress): Future[UInt256] {.async.} =
let hex = await client.eth_getBalance(ethAddressStr(address), "latest")
result = UInt256.fromHex(hex.string)
proc nonceAt*(client: RpcClient, address: EthAddress): Future[AccountNonce] {.async.} =
let hex = await client.eth_getTransactionCount(ethAddressStr(address), "latest")
result = parseHexInt(hex.string).AccountNonce
proc txReceipt*(client: RpcClient, txHash: Hash256): Future[Option[Receipt]] {.async.} =
let rr = await client.eth_getTransactionReceipt(txHash)
if rr.isNone:
return none(Receipt)
let rc = rr.get()
let rec = Receipt(
receiptType: LegacyReceipt,
isHash : rc.root.isSome,
status : rc.status.isSome,
hash : rc.root.get(Hash256()),
cumulativeGasUsed: parseHexInt(rc.cumulativeGasUsed.string).GasInt,
bloom : BloomFilter(rc.logsBloom),
logs : rc.logs
)
result = some(rec)
proc gasUsed*(client: RpcClient, txHash: Hash256): Future[Option[GasInt]] {.async.} =
let rr = await client.eth_getTransactionReceipt(txHash)
if rr.isNone:
return none(GasInt)
let rc = rr.get()
result = some(parseHexInt(rc.gasUsed.string).GasInt)

View File

@ -1,90 +0,0 @@
# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/strutils,
eth/[common],
stew/byteutils,
stint,
chronos,
unittest2,
json_rpc/[rpcclient],
"."/[vault, client]
export client
const
gasPrice* = 30000000000 # 30 Gwei or 30 * pow(10, 9)
chainID* = ChainID(7)
type
TestEnv* = ref object
vault*: Vault
client*: RpcClient
TestSpec* = object
name*: string
run*: proc(t: TestEnv): Future[TestStatus]
func eth(n: int): UInt256 {.compileTime.} =
n.u256 * pow(10.u256, 18)
func u256(x: string): UInt256 =
UInt256.fromHex(x)
func ethAddr(x: string): EthAddress =
hexToByteArray[20](x)
# envTest make sure the env is set up properly for subsequent tests
proc envTest(t: TestEnv): Future[TestStatus] {.async.} =
let client = t.client
let res = await client.web3_clientVersion()
const kv = {
"cf49fda3be353c69b41ed96333cd24302da4556f": "0x123450000000000000000",
"0161e041aad467a890839d5b08b138c1e6373072": "0x123450000000000000000",
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": "0x123450000000000000000",
"b97de4b8c857e4f6bc354f226dc3249aaee49209": "0x123450000000000000000",
"c5065c9eeebe6df2c2284d046bfc906501846c51": "0x123450000000000000000"
}
for x in kv:
let res = await client.balanceAt(ethAddr(x[0]))
let expected = u256(x[1])
if res != expected:
debugEcho "expected: $1, got $2" % [x[0], $res]
return TestStatus.Failed
result = TestStatus.OK
# balanceAndNonceAtTest creates a new account and transfers funds to it.
# It then tests if the balance and nonce of the sender and receiver
# address are updated correct.
proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
let
sourceAddr = await t.vault.createAccount(1.eth)
sourceNonce = 0.AccountNonce
targetAddr = await t.vault.createAccount(0.u256)
# Get current balance
let sourceAddressBalanceBefore = t.client.balanceAt(sourceAddr)
# TODO: complete this test
result = TestStatus.OK
const testList* = [
TestSpec(
name: "env is set up properly for subsequent tests",
run: envTest
),
TestSpec(
name: "balance and nonce update correctly",
run: balanceAndNonceAtTest
)
]

View File

@ -8,64 +8,19 @@
# those terms.
import
std/[os, times],
eth/[trie/db],
eth/p2p as ethp2p,
stew/shims/net as stewNet,
stew/results,
chronos, json_rpc/[rpcserver, rpcclient],
../../../nimbus/db/db_chain,
../../../nimbus/sync/protocol,
../../../nimbus/[config, context, genesis, utils/tx_pool],
../../../nimbus/rpc/[common, p2p, debug],
../../../tests/test_helpers,
"."/[ethclient, vault, client],
std/[times],
chronos,
"."/[rpc_tests, test_env],
../sim_utils
const
initPath = "hive_integration" / "nodocker" / "rpc" / "init"
proc manageAccounts(ctx: EthContext, conf: NimbusConf) =
if string(conf.importKey).len > 0:
let res = ctx.am.importPrivateKey(string conf.importKey)
if res.isErr:
echo res.error()
quit(QuitFailure)
proc setupRpcServer(ctx: EthContext, chainDB: BaseChainDB,
ethNode: EthereumNode, txPool: TxPoolRef,
conf: NimbusConf): RpcServer =
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
setupCommonRpc(ethNode, conf, rpcServer)
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
rpcServer.start()
rpcServer
proc setupWsRpcServer(ctx: EthContext, chainDB: BaseChainDB,
ethNode: EthereumNode, txPool: TxPoolRef,
conf: NimbusConf): RpcServer =
let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort))
setupCommonRpc(ethNode, conf, rpcServer)
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
rpcServer.start()
rpcServer
proc runRpcTest() =
let client = newRpcHttpClient()
waitFor client.connect("127.0.0.1", Port(8545), false)
let testEnv = TestEnv(
client: client,
vault : newVault(chainID, gasPrice, client)
)
var stat: SimStat
let start = getTime()
for x in testList:
try:
let status = waitFor x.run(testEnv)
let env = setupEnv()
let status = waitFor x.run(env)
env.stopEnv()
stat.inc(x.name, status)
except ValueError as ex:
stat.inc(x.name, TestStatus.Failed)
@ -74,38 +29,5 @@ proc runRpcTest() =
let elpd = getTime() - start
print(stat, elpd, "rpc")
proc main() =
let conf = makeConfig(@[
"--prune-mode:archive",
# "--nat:extip:0.0.0.0",
"--network:7",
"--import-key:" & initPath / "private-key",
"--engine-signer:658bdf435d810c91414ec09147daa6db62406379",
"--custom-network:" & initPath / "genesis.json",
"--rpc",
"--rpc-api:eth,debug",
# "--rpc-address:0.0.0.0",
"--rpc-port:8545",
"--ws",
"--ws-api:eth,debug",
# "--ws-address:0.0.0.0",
"--ws-port:8546"
])
let
ethCtx = newEthContext()
ethNode = setupEthNode(conf, ethCtx, eth)
chainDB = newBaseChainDB(newMemoryDb(),
conf.pruneMode == PruneMode.Full,
conf.networkId,
conf.networkParams
)
chainDB.populateProgress()
chainDB.initializeEmptyDb()
let txPool = TxPoolRef.new(chainDB, conf.engineSigner)
let rpcServer = setupRpcServer(ethCtx, chainDB, ethNode, txPool, conf)
runRpcTest()
main()
runRpcTest()

View File

@ -0,0 +1,150 @@
# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/strutils,
eth/[common],
stew/byteutils,
stint,
chronos,
unittest2,
json_rpc/[rpcclient],
"."/[vault, client, test_env]
export client
type
TestSpec* = object
name*: string
run*: proc(t: TestEnv): Future[TestStatus]
func eth(n: int): UInt256 {.compileTime.} =
n.u256 * pow(10.u256, 18)
func u256(x: string): UInt256 =
UInt256.fromHex(x)
func ethAddr(x: string): EthAddress =
hexToByteArray[20](x)
# envTest make sure the env is set up properly for subsequent tests
proc envTest(t: TestEnv): Future[TestStatus] {.async.} =
let client = t.rpcClient
let res = await client.web3_clientVersion()
const kv = {
"cf49fda3be353c69b41ed96333cd24302da4556f": "0x123450000000000000000",
"0161e041aad467a890839d5b08b138c1e6373072": "0x123450000000000000000",
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": "0x123450000000000000000",
"b97de4b8c857e4f6bc354f226dc3249aaee49209": "0x123450000000000000000",
"c5065c9eeebe6df2c2284d046bfc906501846c51": "0x123450000000000000000"
}
for x in kv:
let res = await client.balanceAt(ethAddr(x[0]))
let expected = u256(x[1])
if res != expected:
echo "expected: $1, got $2" % [x[0], $res]
return TestStatus.Failed
result = TestStatus.OK
# balanceAndNonceAtTest creates a new account and transfers funds to it.
# It then tests if the balance and nonce of the sender and receiver
# address are updated correct.
proc balanceAndNonceAtTest(t: TestEnv): Future[TestStatus] {.async.} =
let
client = t.rpcClient
vault = t.vault
sourceAddr = await vault.createAccount(1.eth)
targetAddr = await vault.createAccount(0.u256)
var
sourceNonce = 0.AccountNonce
# Get current balance
let sourceAddressBalanceBefore = await client.balanceAt(sourceAddr)
let expected = 1.eth
if sourceAddressBalanceBefore != expected:
echo "Expected balance $1, got $1" % [$expected, $sourceAddressBalanceBefore]
return TestStatus.Failed
let nonceBefore = await client.nonceAt(sourceAddr)
if nonceBefore != sourceNonce:
echo "Invalid nonce, want $1, got $1" % [$sourceNonce, $nonceBefore]
return TestStatus.Failed
# send 1234 wei to target account and verify balances and nonces are updated
let
amount = 1234.u256
gasLimit = 50000.GasInt
let tx = vault.signTx(sourceAddr, sourceNonce, targetAddr, amount, gasLimit, gasPrice)
inc sourceNonce
let txHash = rlpHash(tx)
echo "BalanceAt: send $1 wei from 0x$2 to 0x$3 in 0x$4" % [
$tx.value, sourceAddr.toHex, targetAddr.toHex, txHash.data.toHex]
let ok = await client.sendTransaction(tx)
if not ok:
echo "failed to send transaction"
return TestStatus.Failed
var gasUsed: GasInt
var loop = 0
while true:
let res = await client.gasUsed(txHash)
if res.isSome:
gasUsed = res.get()
break
let period = chronos.seconds(1)
await sleepAsync(period)
inc loop
if loop == 5:
echo "get gas used timeout"
return TestStatus.Failed
# ensure balances have been updated
let accountBalanceAfter = await client.balanceAt(sourceAddr)
let balanceTargetAccountAfter = await client.balanceAt(targetAddr)
# expected balance is previous balance - tx amount - tx fee (gasUsed * gasPrice)
let exp = sourceAddressBalanceBefore - amount - (gasUsed * tx.gasPrice).u256
if exp != accountBalanceAfter:
echo "Expected sender account to have a balance of $1, got $2" % [$exp, $accountBalanceAfter]
return TestStatus.Failed
if balanceTargetAccountAfter != amount:
echo "Expected new account to have a balance of $1, got $2" % [$tx.value, $balanceTargetAccountAfter]
return TestStatus.Failed
# ensure nonce is incremented by 1
let nonceAfter = await client.nonceAt(sourceAddr)
let expectedNonce = nonceBefore + 1
if expectedNonce != nonceAfter:
echo "Invalid nonce, want $1, got $2" % [$expectedNonce, $nonceAfter]
return TestStatus.Failed
result = TestStatus.OK
const testList* = [
TestSpec(
name: "env is set up properly for subsequent tests",
run: envTest
),
TestSpec(
name: "balance and nonce update correctly",
run: balanceAndNonceAtTest
)
]

View File

@ -0,0 +1,136 @@
# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/[os],
eth/[trie/db],
eth/common as ethcommon,
eth/p2p as ethp2p,
stew/shims/net as stewNet,
stew/results,
chronos, json_rpc/[rpcserver, rpcclient],
../../../nimbus/db/db_chain,
../../../nimbus/sync/protocol,
../../../nimbus/[config, context, genesis, utils/tx_pool, sealer],
../../../nimbus/rpc/[common, p2p, debug],
../../../nimbus/p2p/chain,
../../../tests/test_helpers,
./vault
type
StopServerProc = proc(srv: RpcServer)
TestEnv* = ref object
vault*: Vault
rpcClient*: RpcClient
rpcServer: RpcServer
sealingEngine: SealingEngineRef
stopServer: StopServerProc
const
initPath = "hive_integration" / "nodocker" / "rpc" / "init"
gasPrice* = 30000000000 # 30 Gwei or 30 * pow(10, 9)
chainID* = ChainID(7)
proc manageAccounts(ctx: EthContext, conf: NimbusConf) =
if string(conf.importKey).len > 0:
let res = ctx.am.importPrivateKey(string conf.importKey)
if res.isErr:
echo res.error()
quit(QuitFailure)
proc setupRpcServer(ctx: EthContext, chainDB: BaseChainDB,
ethNode: EthereumNode, txPool: TxPoolRef,
conf: NimbusConf): RpcServer =
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
setupCommonRpc(ethNode, conf, rpcServer)
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
rpcServer.start()
rpcServer
proc setupWsRpcServer(ctx: EthContext, chainDB: BaseChainDB,
ethNode: EthereumNode, txPool: TxPoolRef,
conf: NimbusConf): RpcServer =
let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort))
setupCommonRpc(ethNode, conf, rpcServer)
setupEthRpc(ethNode, ctx, chainDB, txPool, rpcServer)
rpcServer.start()
rpcServer
proc stopRpcHttpServer(srv: RpcServer) =
let rpcServer = RpcHttpServer(srv)
waitFor rpcServer.stop()
waitFor rpcServer.closeWait()
proc stopRpcWsServer(srv: RpcServer) =
let rpcServer = RpcWebSocketServer(srv)
rpcServer.stop()
waitFor rpcServer.closeWait()
proc setupEnv*(): TestEnv =
let conf = makeConfig(@[
"--prune-mode:archive",
# "--nat:extip:0.0.0.0",
"--network:7",
"--import-key:" & initPath / "private-key",
"--engine-signer:658bdf435d810c91414ec09147daa6db62406379",
"--custom-network:" & initPath / "genesis.json",
"--rpc",
"--rpc-api:eth,debug",
# "--rpc-address:0.0.0.0",
"--rpc-port:8545",
"--ws",
"--ws-api:eth,debug",
# "--ws-address:0.0.0.0",
"--ws-port:8546"
])
let
ethCtx = newEthContext()
ethNode = setupEthNode(conf, ethCtx, eth)
chainDB = newBaseChainDB(newMemoryDb(),
conf.pruneMode == PruneMode.Full,
conf.networkId,
conf.networkParams
)
manageAccounts(ethCtx, conf)
chainDB.populateProgress()
chainDB.initializeEmptyDb()
let chainRef = newChain(chainDB)
let txPool = TxPoolRef.new(chainDB, conf.engineSigner)
let sealingEngine = SealingEngineRef.new(
chainRef, ethCtx, conf.engineSigner,
txPool, EngineStopped
)
let rpcServer = setupRpcServer(ethCtx, chainDB, ethNode, txPool, conf)
let rpcClient = newRpcHttpClient()
waitFor rpcClient.connect("127.0.0.1", Port(8545), false)
let stopServer = stopRpcHttpServer
sealingEngine.start()
let t = TestEnv(
rpcClient: rpcClient,
sealingEngine: sealingEngine,
rpcServer: rpcServer,
vault : newVault(chainID, gasPrice, rpcClient),
stopServer: stopServer
)
result = t
proc stopEnv*(t: TestEnv) =
waitFor t.rpcClient.close()
waitFor t.sealingEngine.stop()
t.stopServer(t.rpcServer)

View File

@ -93,6 +93,29 @@ proc makeFundingTx*(v: Vault, recipient: EthAddress, amount: UInt256): Transacti
signTransaction(unsignedTx, v.vaultKey, v.chainId, eip155 = true)
proc signTx*(v: Vault,
sender: EthAddress,
nonce: AccountNonce,
recipient: EthAddress,
amount: UInt256,
gasLimit, gasPrice: GasInt,
payload: seq[byte] = @[]): Transaction =
let
unsignedTx = Transaction(
txType : TxLegacy,
chainId : v.chainId,
nonce : nonce,
gasPrice: gasPrice,
gasLimit: gasLimit,
to : some(recipient),
value : amount,
payload : payload
)
let key = v.accounts[sender]
signTransaction(unsignedTx, key, v.chainId, eip155 = true)
# createAccount creates a new account that is funded from the vault contract.
# It will panic when the account could not be created and funded.
proc createAccount*(v: Vault, amount: UInt256): Future[EthAddress] {.async.} =