hive: nodocker rpc simulator skeleton
This commit is contained in:
parent
f051c2530e
commit
cdfcdfc85b
|
@ -0,0 +1,18 @@
|
|||
# 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, strutils],
|
||||
eth/[common],
|
||||
json_rpc/[rpcclient],
|
||||
../../../nimbus/rpc/[hexstrings, rpc_types]
|
||||
|
||||
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||
const sigPath = sourceDir / ".." / ".." / ".." / "tests" / "rpcclient" / "ethcallsigs.nim"
|
||||
createRpcSigs(RpcClient, sigPath)
|
|
@ -0,0 +1,40 @@
|
|||
# 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, keys, rlp],
|
||||
stew/byteutils,
|
||||
chronos, stint,
|
||||
json_rpc/[rpcclient],
|
||||
../../../nimbus/[utils, transaction],
|
||||
../../../nimbus/rpc/hexstrings,
|
||||
"."/[callsigs]
|
||||
|
||||
proc fromHex(x: type Hash256, hex: EthHashStr): Hash256 =
|
||||
hexToByteArray(hex.string, result.data)
|
||||
|
||||
proc sendTransaction*(client: RpcClient, tx: Transaction): Future[bool] {.async.} =
|
||||
let data = rlp.encode(tx)
|
||||
let txHash = keccakHash(data)
|
||||
let hex = await client.eth_sendRawTransaction(hexDataStr(data))
|
||||
let decodedHash = Hash256.fromHex(hex)
|
||||
result = decodedHash == txHash
|
||||
|
||||
proc blockNumber*(client: RpcClient): Future[uint64] {.async.} =
|
||||
let hex = await client.eth_blockNumber()
|
||||
result = parseHexInt(hex.string).uint64
|
||||
|
||||
proc balanceAt*(client: RpcClient, address: EthAddress, blockNumber: uint64): Future[UInt256] {.async.} =
|
||||
let hex = await client.eth_getBalance(ethAddressStr(address), encodeQuantity(blockNumber).string)
|
||||
result = UInt256.fromHex(hex.string)
|
||||
|
||||
proc balanceAt*(client: RpcClient, address: EthAddress): Future[UInt256] {.async.} =
|
||||
let hex = await client.eth_getBalance(ethAddressStr(address), "latest")
|
||||
result = UInt256.fromHex(hex.string)
|
|
@ -0,0 +1,71 @@
|
|||
# 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,
|
||||
json_rpc/[rpcclient],
|
||||
"."/[vault, client, callsigs]
|
||||
|
||||
const
|
||||
gasPrice* = 30000000000 # 30 Gwei or 30 * pow(10, 9)
|
||||
chainID* = ChainID(7)
|
||||
|
||||
type
|
||||
TestEnv* = ref object
|
||||
vault*: Vault
|
||||
client*: RpcClient
|
||||
|
||||
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[bool] {.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 false
|
||||
|
||||
result = true
|
||||
|
||||
# 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) {.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
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"genesis": {
|
||||
"coinbase": "0x0000000000000000000000000000000000000000",
|
||||
"difficulty": "0x20000",
|
||||
"extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
"gasLimit": "0x2fefd8",
|
||||
"nonce": "0x0000000000000000",
|
||||
"timestamp": "0x1234",
|
||||
"alloc": {
|
||||
"cf49fda3be353c69b41ed96333cd24302da4556f": {
|
||||
"balance": "0x123450000000000000000"
|
||||
},
|
||||
"0161e041aad467a890839d5b08b138c1e6373072": {
|
||||
"balance": "0x123450000000000000000"
|
||||
},
|
||||
"87da6a8c6e9eff15d703fc2773e32f6af8dbe301": {
|
||||
"balance": "0x123450000000000000000"
|
||||
},
|
||||
"b97de4b8c857e4f6bc354f226dc3249aaee49209": {
|
||||
"balance": "0x123450000000000000000"
|
||||
},
|
||||
"c5065c9eeebe6df2c2284d046bfc906501846c51": {
|
||||
"balance": "0x123450000000000000000"
|
||||
},
|
||||
"0000000000000000000000000000000000000314": {
|
||||
"balance": "0x0",
|
||||
"code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029",
|
||||
"storage": {
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234",
|
||||
"0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01"
|
||||
}
|
||||
},
|
||||
"0000000000000000000000000000000000000315": {
|
||||
"balance": "0x9999999999999999999999999999999",
|
||||
"code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029"
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"clique": {
|
||||
"period": 1
|
||||
},
|
||||
"chainId": 7,
|
||||
"homesteadBlock": 0,
|
||||
"eip150Block": 0,
|
||||
"eip155Block": 0,
|
||||
"eip158Block": 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
9c647b8b7c4e7c3490668fb6c11473619db80c93704c70893d3813af4090c39c
|
|
@ -0,0 +1,95 @@
|
|||
# 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, asynctest,
|
||||
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_eth65,
|
||||
../../../nimbus/[config, context, genesis],
|
||||
../../../nimbus/rpc/[common, p2p, debug],
|
||||
../../../tests/test_helpers,
|
||||
"."/[callsigs, ethclient, vault, client]
|
||||
|
||||
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, conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcHttpServer([initTAddress(conf.rpcAddress, conf.rpcPort)])
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, rpcServer)
|
||||
|
||||
rpcServer.start()
|
||||
rpcServer
|
||||
|
||||
proc setupWsRpcServer(ctx: EthContext, chainDB: BaseChainDB, ethNode: EthereumNode, conf: NimbusConf): RpcServer =
|
||||
let rpcServer = newRpcWebSocketServer(initTAddress(conf.wsAddress, conf.wsPort))
|
||||
setupCommonRpc(ethNode, conf, rpcServer)
|
||||
setupEthRpc(ethNode, ctx, chainDB, 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)
|
||||
)
|
||||
|
||||
suite "JSON RPC Test Over HTTP":
|
||||
test "env test":
|
||||
check await envTest(testEnv)
|
||||
|
||||
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 rpcServer = setupRpcServer(ethCtx, chainDB, ethNode, conf)
|
||||
runRpcTest()
|
||||
|
||||
main()
|
|
@ -0,0 +1,124 @@
|
|||
# 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/[tables, strutils],
|
||||
eth/[common, keys, rlp],
|
||||
chronos, stint,
|
||||
stew/byteutils,
|
||||
json_rpc/[rpcclient],
|
||||
../../../nimbus/[utils, transaction],
|
||||
../../../nimbus/rpc/hexstrings,
|
||||
"."/client
|
||||
|
||||
const
|
||||
# This is the account that sends vault funding transactions.
|
||||
vaultAccountAddr = hextoByteArray[20]("0xcf49fda3be353c69b41ed96333cd24302da4556f")
|
||||
|
||||
const
|
||||
# Address of the vault in genesis.
|
||||
predeployedVaultAddr = hextoByteArray[20]("0000000000000000000000000000000000000315")
|
||||
# Number of blocks to wait before funding tx is considered valid.
|
||||
vaultTxConfirmationCount = 5
|
||||
|
||||
# vault creates accounts for testing and funds them. An instance of the vault contract is
|
||||
# deployed in the genesis block. When creating a new account using createAccount, the
|
||||
# account is funded by sending a transaction to this contract.
|
||||
#
|
||||
# The purpose of the vault is allowing tests to run concurrently without worrying about
|
||||
# nonce assignment and unexpected balance changes.
|
||||
type
|
||||
Vault* = ref object
|
||||
# This tracks the account nonce of the vault account.
|
||||
nonce: AccountNonce
|
||||
# Created accounts are tracked in this map.
|
||||
accounts: Table[EthAddress, PrivateKey]
|
||||
|
||||
rng: ref BrHmacDrbgContext
|
||||
chainID: ChainID
|
||||
gasPrice: GasInt
|
||||
vaultKey: PrivateKey
|
||||
client: RpcClient
|
||||
|
||||
proc newVault*(chainID: ChainID, gasPrice: GasInt, client: RpcClient): Vault =
|
||||
new(result)
|
||||
result.rng = newRng()
|
||||
result.chainID = chainID
|
||||
result.gasPrice = gasPrice
|
||||
result.vaultKey = PrivateKey.fromHex("63b508a03c3b5937ceb903af8b1b0c191012ef6eb7e9c3fb7afa94e5d214d376").get()
|
||||
result.client = client
|
||||
|
||||
# generateKey creates a new account key and stores it.
|
||||
proc generateKey*(v: Vault): EthAddress =
|
||||
let key = PrivateKey.random(v.rng[])
|
||||
let address = toCanonicalAddress(key.toPublicKey)
|
||||
v.accounts[address] = key
|
||||
address
|
||||
|
||||
# nextNonce generates the nonce of a funding transaction.
|
||||
proc nextNonce*(v: Vault): AccountNonce =
|
||||
let nonce = v.nonce
|
||||
inc(v.nonce)
|
||||
nonce
|
||||
|
||||
proc sendSome(address: EthAddress, amount: UInt256): seq[byte] =
|
||||
const padding = repeat('\0', 12).toBytes
|
||||
# makeshift contract ABI construction
|
||||
# https://docs.soliditylang.org/en/develop/abi-spec.html
|
||||
let h = keccakHash("sendSome(address,uint256)".toBytes)
|
||||
result.add h.data[0..3] # first 4 bytes of hash
|
||||
result.add padding # left pad address
|
||||
result.add address
|
||||
result.add amount.toBytesBE
|
||||
doAssert(result.len == 68) # 4 + 32 + 32
|
||||
|
||||
proc makeFundingTx*(v: Vault, recipient: EthAddress, amount: UInt256): Transaction =
|
||||
let
|
||||
unsignedTx = Transaction(
|
||||
txType : TxLegacy,
|
||||
chainId : v.chainId,
|
||||
nonce : v.nextNonce(),
|
||||
gasPrice: v.gasPrice,
|
||||
gasLimit: GasInt(75000),
|
||||
to : some(predeployedVaultAddr),
|
||||
value : 0.u256,
|
||||
payload : sendSome(recipient, amount)
|
||||
)
|
||||
|
||||
signTransaction(unsignedTx, v.vaultKey, 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.} =
|
||||
let address = v.generateKey()
|
||||
|
||||
# order the vault to send some ether
|
||||
let tx = v.makeFundingTx(address, amount)
|
||||
let res = await v.client.sendTransaction(tx)
|
||||
if not res:
|
||||
raise newException(ValueError, "unable to send funding transaction")
|
||||
|
||||
let txBlock = await v.client.blockNumber()
|
||||
|
||||
# wait for vaultTxConfirmationCount confirmation by checking the balance vaultTxConfirmationCount blocks back.
|
||||
# createAndFundAccountWithSubscription for a better solution using logs
|
||||
let count = vaultTxConfirmationCount*4
|
||||
for i in 0..<count:
|
||||
let number = await v.client.blockNumber()
|
||||
if number > txBlock + vaultTxConfirmationCount:
|
||||
let checkBlock = number - vaultTxConfirmationCount
|
||||
let balance = await v.client.balanceAt(address, checkBlock)
|
||||
if balance >= amount:
|
||||
return address
|
||||
|
||||
let period = chronos.seconds(1)
|
||||
await sleepAsync(period)
|
||||
|
||||
let txHash = tx.rlpHash().data.toHex
|
||||
raise newException(ValueError, "could not fund account $2 in transaction $2" % [address.toHex, txHash])
|
Loading…
Reference in New Issue