diff --git a/hive_integration/nodocker/rpc/client.nim b/hive_integration/nodocker/rpc/client.nim index 60057e888..26ace2dfa 100644 --- a/hive_integration/nodocker/rpc/client.nim +++ b/hive_integration/nodocker/rpc/client.nim @@ -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) diff --git a/hive_integration/nodocker/rpc/ethclient.nim b/hive_integration/nodocker/rpc/ethclient.nim deleted file mode 100644 index 9882ca7d3..000000000 --- a/hive_integration/nodocker/rpc/ethclient.nim +++ /dev/null @@ -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 - ) -] diff --git a/hive_integration/nodocker/rpc/rpc_sim.nim b/hive_integration/nodocker/rpc/rpc_sim.nim index 0bf562745..2df93ac57 100644 --- a/hive_integration/nodocker/rpc/rpc_sim.nim +++ b/hive_integration/nodocker/rpc/rpc_sim.nim @@ -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() diff --git a/hive_integration/nodocker/rpc/rpc_tests.nim b/hive_integration/nodocker/rpc/rpc_tests.nim new file mode 100644 index 000000000..74fc80f06 --- /dev/null +++ b/hive_integration/nodocker/rpc/rpc_tests.nim @@ -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 + ) +] diff --git a/hive_integration/nodocker/rpc/test_env.nim b/hive_integration/nodocker/rpc/test_env.nim new file mode 100644 index 000000000..d9d38d2b5 --- /dev/null +++ b/hive_integration/nodocker/rpc/test_env.nim @@ -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) diff --git a/hive_integration/nodocker/rpc/vault.nim b/hive_integration/nodocker/rpc/vault.nim index a87710237..ec190df78 100644 --- a/hive_integration/nodocker/rpc/vault.nim +++ b/hive_integration/nodocker/rpc/vault.nim @@ -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.} =