implement web3, net, and some eth namespace rpc

This commit is contained in:
jangko 2020-07-22 23:51:26 +07:00
parent 032c29288a
commit 336efdb0c3
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
11 changed files with 317 additions and 127 deletions

View File

@ -18,6 +18,12 @@ type
pruneTrie*: bool
config* : ChainConfig
# startingBlock, currentBlock, and highestBlock
# are progress indicator
startingBlock*: BlockNumber
currentBlock*: BlockNumber
highestBlock*: BlockNumber
#KeyType = enum
# blockNumberToHash
# blockHashToScore
@ -64,6 +70,15 @@ proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
raise newException(CanonicalHeadNotFound,
"No canonical head set for this chain")
proc populateProgress*(self: BaseChainDB) =
try:
self.startingBlock = self.getCanonicalHead().blockNumber
except CanonicalHeadNotFound:
self.startingBlock = toBlockNumber(0)
self.currentBlock = self.startingBlock
self.highestBlock = self.startingBlock
proc getBlockHash*(self: BaseChainDB, n: BlockNumber, output: var Hash256): bool {.inline.} =
## Return the block hash for the given block number.
self.getHash(blockNumberToHashKey(n), output)
@ -127,7 +142,7 @@ proc persistTransactions*(self: BaseChainDB, blockNumber: BlockNumber, transacti
trie.put(rlp.encode(idx), encodedTx)
self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey))
iterator getBlockTransactionData(self: BaseChainDB, transactionRoot: Hash256): seq[byte] =
iterator getBlockTransactionData*(self: BaseChainDB, transactionRoot: Hash256): seq[byte] =
var transactionDb = initHexaryTrie(self.db, transactionRoot)
var transactionIdx = 0
while true:
@ -144,6 +159,17 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader):
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
yield keccakHash(encodedTx)
proc getTransactionCount*(chain: BaseChainDB, blockHash: Hash256): int =
var header: BlockHeader
if chain.getBlockHeader(blockHash, header):
var trie = initHexaryTrie(chain.db, header.txRoot)
var txCount = 0
while true:
let txKey = rlp.encode(txCount)
if txKey notin trie:
break
inc txCount
proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool =
var header: BlockHeader
if self.getBlockHeader(blockHash, header):

View File

@ -52,11 +52,6 @@ proc start(nimbus: NimbusNode) =
discard setTimer(Moment.fromNow(conf.debug.logMetricsInterval.seconds), logMetrics)
discard setTimer(Moment.fromNow(conf.debug.logMetricsInterval.seconds), logMetrics)
## Creating RPC Server
if RpcFlags.Enabled in conf.rpc.flags:
nimbus.rpcServer = newRpcHttpServer(conf.rpc.binds)
setupCommonRpc(nimbus.rpcServer)
## Creating P2P Server
let keypair = conf.net.nodekey.toKeyPair()
@ -88,6 +83,8 @@ proc start(nimbus: NimbusNode) =
conf.prune == PruneMode.Full,
conf.net.networkId.toPublicNetwork())
chainDB.populateProgress()
if canonicalHeadHashKey().toOpenArray notin trieDB:
initializeEmptyDb(chainDb)
doAssert(canonicalHeadHashKey().toOpenArray in trieDB)
@ -107,6 +104,11 @@ proc start(nimbus: NimbusNode) =
nimbus.ethNode.chain = newChain(chainDB)
## Creating RPC Server
if RpcFlags.Enabled in conf.rpc.flags:
nimbus.rpcServer = newRpcHttpServer(conf.rpc.binds)
setupCommonRpc(nimbus.ethNode, nimbus.rpcServer)
# Enable RPC APIs based on RPC flags and protocol flags
if RpcFlags.Eth in conf.rpc.flags and ProtocolFlags.Eth in conf.net.protocols:
setupEthRpc(nimbus.ethNode, chainDB, nimbus.rpcServer)

View File

@ -3,6 +3,9 @@ import ../db/db_chain, eth/common, chronicles, ../vm_state, ../vm_types,
../utils, eth/trie/db, ./executor, ../config, ../genesis, ../utils,
stew/endians2
when not defined(release):
import ../tracer
type
# Chain's forks not always equals to EVM's forks
ChainFork = enum
@ -125,6 +128,7 @@ method persistBlocks*(c: Chain, headers: openarray[BlockHeader], bodies: openarr
debug "Number of headers not matching number of bodies"
return ValidationResult.Error
c.db.highestBlock = headers[^1].blockNumber
let transaction = c.db.db.beginTransaction()
defer: transaction.dispose()
@ -151,6 +155,11 @@ method persistBlocks*(c: Chain, headers: openarray[BlockHeader], bodies: openarr
c.db.persistTransactions(headers[i].blockNumber, bodies[i].transactions)
c.db.persistReceipts(vmState.receipts)
# update currentBlock *after* we persist it
# so the rpc return consistent result
# between eth_blockNumber and eth_syncing
c.db.currentBlock = headers[i].blockNumber
transaction.commit()
method getTrieDB*(c: Chain): TrieDatabaseRef {.gcsafe.} =

View File

@ -8,14 +8,28 @@
# those terms.
import
strutils,
strutils, tables,
nimcrypto, eth/common as eth_common, stint, json_rpc/server,
eth/p2p,
../config, hexstrings
proc setupCommonRPC*(server: RpcServer) =
proc setupCommonRPC*(node: EthereumNode, server: RpcServer) =
server.rpc("web3_clientVersion") do() -> string:
result = NimbusIdent
server.rpc("web3_sha3") do(data: HexDataStr) -> string:
var rawdata = nimcrypto.fromHex(data.string[2 .. ^1])
result = "0x" & $keccak_256.digest(rawdata)
server.rpc("net_version") do() -> string:
let conf = getConfiguration()
result = $conf.net.networkId
server.rpc("net_listening") do() -> bool:
let conf = getConfiguration()
let numPeers = node.peerPool.connectedNodes.len
result = numPeers < conf.net.maxPeers
server.rpc("net_peerCount") do() -> HexQuantityStr:
let peerCount = uint node.peerPool.connectedNodes.len
result = encodeQuantity(peerCount)

View File

@ -34,8 +34,8 @@ import
type
HexQuantityStr* = distinct string
HexDataStr* = distinct string
EthAddressStr* = distinct string # Same as HexDataStr but must be less <= 20 bytes
EthHashStr* = distinct string # Same as HexDataStr but must be exactly 32 bytes
EthAddressStr* = distinct string # Same as HexDataStr but must be less <= 20 bytes
EthHashStr* = distinct string # Same as HexDataStr but must be exactly 32 bytes
Identifier* = distinct string # 32 bytes, no 0x prefix!
HexStrings = HexQuantityStr | HexDataStr | EthAddressStr | EthHashStr |
Identifier
@ -51,9 +51,13 @@ template stripLeadingZeros(value: string): string =
cidx.inc
value[cidx .. ^1]
func encodeQuantity*(value: SomeUnsignedInt): string {.inline.} =
func encodeQuantity*(value: SomeUnsignedInt): HexQuantityStr {.inline.} =
var hValue = value.toHex.stripLeadingZeros
result = "0x" & hValue
result = HexQuantityStr("0x" & hValue)
func encodeQuantity*(value: UInt256): HexQuantityStr {.inline.} =
var hValue = value.toHex
result = HexQuantityStr("0x" & hValue)
template hasHexHeader(value: string): bool =
if value.len >= 2 and value[0] == '0' and value[1] in {'x', 'X'}: true
@ -65,6 +69,15 @@ template isHexChar(c: char): bool =
c notin {'A'..'F'}: false
else: true
func `==`*(a, b: HexQuantityStr): bool {.inline.} =
a.string == b.string
func `==`*(a, b: EthAddressStr): bool {.inline.} =
a.string == b.string
func `==`*(a, b: HexDataStr): bool {.inline.} =
a.string == b.string
func isValidHexQuantity*(value: string): bool =
if not value.hasHexHeader:
return false
@ -158,10 +171,19 @@ proc hexDataStr*(value: string): HexDataStr {.inline.} =
value.validateHexData
result = value.HexDataStr
proc hexDataStr*(value: openArray[byte]): HexDataStr {.inline.} =
result = HexDataStr("0x" & value.toHex)
proc hexDataStr*(value: Uint256): HexDataStr {.inline.} =
result = HexDataStr("0x" & toBytesBE(value).toHex)
proc ethAddressStr*(value: string): EthAddressStr {.inline.} =
value.validateHexAddressStr
result = value.EthAddressStr
func ethAddressStr*(x: EthAddress): EthAddressStr {.inline.} =
result = EthAddressStr("0x" & toHex(x))
proc ethHashStr*(value: string): EthHashStr {.inline.} =
value.validateHashStr
result = value.EthHashStr
@ -175,7 +197,6 @@ proc `%`*(value: HexStrings): JsonNode =
result = %(value.string)
# Overloads to support expected representation of hex data
proc `%`*(value: EthAddress): JsonNode =
result = %("0x" & value.toHex)

View File

@ -8,12 +8,13 @@
# those terms.
import
strutils, times, options,
strutils, times, options, tables,
json_rpc/rpcserver, hexstrings, stint, stew/byteutils,
eth/[common, keys, rlp, p2p], nimcrypto,
eth/p2p/rlpx_protocols/eth_protocol,
../transaction, ../config, ../vm_state, ../constants, ../vm_types,
../vm_state_transactions, ../utils,
../db/[db_chain, accounts_cache],
../db/[db_chain, state_db],
rpc_types, rpc_utils, ../vm/[message, computation],
../vm/interpreter/vm_forks
@ -27,7 +28,7 @@ import
]#
# Work around for https://github.com/nim-lang/Nim/issues/8645
proc `%`*(value: Time): JsonNode =
#[proc `%`*(value: Time): JsonNode =
result = %value.toUnix
template balance(addressDb: ReadOnlyStateDb, address: EthAddress): GasInt =
@ -83,65 +84,66 @@ proc binarySearchGas(vmState: var BaseVMState, transaction: Transaction, sender:
else:
maxVal = midPoint
result = minVal
proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
]#
proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
proc getAccountDb(header: BlockHeader): ReadOnlyStateDB =
## Retrieves the account db from canonical head
let ac = AccountsCache.init(chain.db, header.stateRoot, chain.pruneTrie)
# we don't use accounst_cache here because it's only read operations
let ac = newAccountStateDB(chain.db, header.stateRoot, chain.pruneTrie)
result = ReadOnlyStateDB(ac)
proc accountDbFromTag(tag: string, readOnly = true): ReadOnlyStateDB =
result = getAccountDb(chain.headerFromTag(tag))
proc getBlockBody(hash: KeccakHash): BlockBody =
#[proc getBlockBody(hash: KeccakHash): BlockBody =
if not chain.getBlockBody(hash, result):
raise newException(ValueError, "Cannot find hash")
raise newException(ValueError, "Cannot find hash")]#
rpcsrv.rpc("net_version") do() -> uint:
let conf = getConfiguration()
result = conf.net.networkId
server.rpc("eth_protocolVersion") do() -> string:
result = $eth_protocol.protocolVersion
rpcsrv.rpc("eth_syncing") do() -> JsonNode:
server.rpc("eth_syncing") do() -> JsonNode:
## Returns SyncObject or false when not syncing.
# TODO: Requires PeerPool to check sync state.
# TODO: Use variant objects
var
sync: SyncState
if true:
# TODO: Populate sync state, this is a placeholder
sync.startingBlock = GENESIS_BLOCK_NUMBER
sync.currentBlock = chain.getCanonicalHead().blockNumber
sync.highestBlock = chain.getCanonicalHead().blockNumber
let numPeers = node.peerPool.connectedNodes.len
if numPeers > 0:
var sync = SyncState(
startingBlock: encodeQuantity chain.startingBlock,
currentBlock : encodeQuantity chain.currentBlock,
highestBlock : encodeQuantity chain.highestBlock
)
result = %sync
else:
result = newJBool(false)
rpcsrv.rpc("eth_coinbase") do() -> EthAddress:
server.rpc("eth_coinbase") do() -> EthAddress:
## Returns the current coinbase address.
result = chain.getCanonicalHead().coinbase
# currently we don't have miner
result = default(EthAddress)
rpcsrv.rpc("eth_mining") do() -> bool:
server.rpc("eth_mining") do() -> bool:
## Returns true if the client is mining, otherwise false.
discard
# currently we don't have miner
result = false
rpcsrv.rpc("eth_hashrate") do() -> int:
server.rpc("eth_hashrate") do() -> HexQuantityStr:
## Returns the number of hashes per second that the node is mining with.
discard
# currently we don't have miner
result = encodeQuantity(0.uint)
rpcsrv.rpc("eth_gasPrice") do() -> int64:
server.rpc("eth_gasPrice") do() -> HexQuantityStr:
## Returns an integer of the current gas price in wei.
discard
result = encodeQuantity(calculateMedianGasPrice(chain).uint64)
rpcsrv.rpc("eth_accounts") do() -> seq[EthAddressStr]:
server.rpc("eth_accounts") do() -> seq[EthAddressStr]:
## Returns a list of addresses owned by client.
result = @[]
rpcsrv.rpc("eth_blockNumber") do() -> BlockNumber:
server.rpc("eth_blockNumber") do() -> HexQuantityStr:
## Returns integer of the current block number the client is on.
result = chain.getCanonicalHead().blockNumber
result = encodeQuantity(chain.getCanonicalHead().blockNumber)
rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> UInt256:
server.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> HexQuantityStr:
## Returns the balance of the account of given address.
##
## data: address to check for balance.
@ -149,12 +151,11 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns integer of the current balance in wei.
let
accountDb = accountDbFromTag(quantityTag)
addrBytes = data.toAddress
balance = accountDb.getBalance(addrBytes)
address = data.toAddress
balance = accountDb.getBalance(address)
result = encodeQuantity(balance)
result = balance
rpcsrv.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: int, quantityTag: string) -> UInt256:
server.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string) -> HexDataStr:
## Returns the value from a storage position at a given address.
##
## data: address of the storage.
@ -163,29 +164,33 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns: the value at this storage position.
let
accountDb = accountDbFromTag(quantityTag)
addrBytes = data.toAddress
result = accountDb.getStorage(addrBytes, quantity.u256)
address = data.toAddress
key = fromHex(Uint256, quantity.string)
value = accountDb.getStorage(address, key)[0]
result = hexDataStr(value)
rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> AccountNonce:
server.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> HexQuantityStr:
## Returns the number of transactions sent from an address.
##
## data: address.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of the number of transactions send from this address.
let
addrBytes = data.toAddress
address = data.toAddress
accountDb = accountDbFromTag(quantityTag)
result = accountDb.getNonce(addrBytes)
result = encodeQuantity(accountDb.getNonce(address))
rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> int:
server.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> HexQuantityStr:
## Returns the number of transactions in a block from a block matching the given block hash.
##
## data: hash of a block
## Returns integer of the number of transactions in this block.
var hashData = data.toHash
result = getBlockBody(hashData).transactions.len
rpcsrv.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int:
let
hashData = data.toHash
txCount = chain.getTransactionCount(hashData)
result = encodeQuantity(txCount.uint)
#[
server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int:
## Returns the number of transactions in a block matching the given block number.
##
## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
@ -193,7 +198,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let header = chain.headerFromTag(quantityTag)
result = getBlockBody(header.hash).transactions.len
rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> int:
server.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> int:
## Returns the number of uncles in a block from a block matching the given block hash.
##
## data: hash of a block.
@ -201,7 +206,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
var hashData = data.toHash
result = getBlockBody(hashData).uncles.len
rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int:
server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int:
## Returns the number of uncles in a block from a block matching the given block number.
##
## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter.
@ -209,7 +214,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let header = chain.headerFromTag(quantityTag)
result = getBlockBody(header.hash).uncles.len
rpcsrv.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr:
server.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr:
## Returns code at a given address.
##
## data: address
@ -217,8 +222,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns the code from the given address.
let
accountDb = accountDbFromTag(quantityTag)
addrBytes = toAddress(data)
storage = accountDb.getCode(addrBytes)
address = toAddress(data)
storage = accountDb.getCode(address)
# Easier to return the string manually here rather than expect ByteRange to be marshalled
result = byteutils.toHex(storage).HexDataStr
@ -227,7 +232,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message
$sign(privateKey, msgData.toBytes())
rpcsrv.rpc("eth_sign") do(data: EthAddressStr, message: HexDataStr) -> HexDataStr:
server.rpc("eth_sign") do(data: EthAddressStr, message: HexDataStr) -> HexDataStr:
## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).
## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature.
## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.
@ -250,7 +255,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
# s = 0.u256
# result = initTransaction(send.nonce, send.gasPrice, send.gas, destination, send.value, data, v, r, s, contractCreation)
rpcsrv.rpc("eth_sendTransaction") do(obj: EthSend) -> HexDataStr:
server.rpc("eth_sendTransaction") do(obj: EthSend) -> HexDataStr:
## Creates new message call transaction or a contract creation, if the data field contains code.
##
## obj: the transaction object.
@ -259,7 +264,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
# TODO: Relies on pending pool implementation
discard
rpcsrv.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> HexDataStr:
server.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> HexDataStr:
## Creates new message call transaction or a contract creation for signed transactions.
##
## data: the signed transaction data.
@ -293,7 +298,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
result = newComputation(vmState, message)
rpcsrv.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr:
server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> HexDataStr:
## Executes a new message call immediately without creating a transaction on the block chain.
##
## call: the transaction call object.
@ -329,7 +334,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
comp.execComputation
result = ("0x" & nimcrypto.toHex(comp.output)).HexDataStr
rpcsrv.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> GasInt:
server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> GasInt:
## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than
## the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.
@ -402,7 +407,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
for i in 0 ..< blockBody.uncles.len:
result.uncles[i] = blockBody.uncles[i].hash
rpcsrv.rpc("eth_getBlockByHash") do(data: EthHashStr, fullTransactions: bool) -> Option[BlockObject]:
server.rpc("eth_getBlockByHash") do(data: EthHashStr, fullTransactions: bool) -> Option[BlockObject]:
## Returns information about a block by hash.
##
## data: Hash of a block.
@ -413,7 +418,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
header = chain.getBlockHeader(h)
result = some(populateBlockObject(header, getBlockBody(h)))
rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> Option[BlockObject]:
server.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> Option[BlockObject]:
## Returns information about a block by block number.
##
## quantityTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
@ -446,7 +451,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
result.gas = accountGas
result.input = transaction.payload
rpcsrv.rpc("eth_getTransactionByHash") do(data: EthHashStr) -> TransactionObject:
server.rpc("eth_getTransactionByHash") do(data: EthHashStr) -> TransactionObject:
## Returns the information about a transaction requested by transaction hash.
##
## data: hash of a transaction.
@ -461,7 +466,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
# TODO: if the requested transaction not in blockchain
# try to look for pending transaction in txpool
rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> TransactionObject:
server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> TransactionObject:
## Returns information about a transaction by block hash and transaction index position.
##
## data: hash of a block.
@ -473,7 +478,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
transaction = getBlockBody(blockHash).transactions[quantity]
result = populateTransactionObject(transaction, quantity, header, blockHash)
rpcsrv.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> TransactionObject:
server.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> TransactionObject:
## Returns information about a transaction by block number and transaction index position.
##
## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
@ -513,7 +518,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
# 1 = success, 0 = failure.
result.status = some(receipt.status)
rpcsrv.rpc("eth_getTransactionReceipt") do(data: EthHashStr) -> ReceiptObject:
server.rpc("eth_getTransactionReceipt") do(data: EthHashStr) -> ReceiptObject:
## Returns the receipt of a transaction by transaction hash.
##
## data: hash of a transaction.
@ -534,7 +539,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
return populateReceipt(receipt, gasUsed, body.transactions[txDetails.index], txDetails.index, header)
idx.inc
rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> Option[BlockObject]:
server.rpc("eth_getUncleByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> Option[BlockObject]:
## Returns information about a uncle of a block by hash and uncle index position.
##
## data: hash of block.
@ -548,7 +553,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let uncle = body.uncles[quantity]
result = some(populateBlockObject(uncle, body))
rpcsrv.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> Option[BlockObject]:
server.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> Option[BlockObject]:
# Returns information about a uncle of a block by number and uncle index position.
##
## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
@ -562,7 +567,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
let uncle = body.uncles[quantity]
result = some(populateBlockObject(uncle, body))
rpcsrv.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
## Creates a filter object, based on filter options, to notify when the state changes (logs).
## To check if the state has changed, call eth_getFilterChanges.
## Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters:
@ -576,21 +581,21 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns integer filter id.
discard
rpcsrv.rpc("eth_newBlockFilter") do() -> int:
server.rpc("eth_newBlockFilter") do() -> int:
## Creates a filter in the node, to notify when a new block arrives.
## To check if the state has changed, call eth_getFilterChanges.
##
## Returns integer filter id.
discard
rpcsrv.rpc("eth_newPendingTransactionFilter") do() -> int:
server.rpc("eth_newPendingTransactionFilter") do() -> int:
## Creates a filter in the node, to notify when a new block arrives.
## To check if the state has changed, call eth_getFilterChanges.
##
## Returns integer filter id.
discard
rpcsrv.rpc("eth_uninstallFilter") do(filterId: int) -> bool:
server.rpc("eth_uninstallFilter") do(filterId: int) -> bool:
## Uninstalls a filter with given id. Should always be called when watch is no longer needed.
## Additonally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.
##
@ -598,23 +603,23 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns true if the filter was successfully uninstalled, otherwise false.
discard
rpcsrv.rpc("eth_getFilterChanges") do(filterId: int) -> seq[FilterLog]:
server.rpc("eth_getFilterChanges") do(filterId: int) -> seq[FilterLog]:
## Polling method for a filter, which returns an list of logs which occurred since last poll.
##
## filterId: the filter id.
result = @[]
rpcsrv.rpc("eth_getFilterLogs") do(filterId: int) -> seq[FilterLog]:
server.rpc("eth_getFilterLogs") do(filterId: int) -> seq[FilterLog]:
## filterId: the filter id.
## Returns a list of all logs matching filter with given id.
result = @[]
rpcsrv.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[FilterLog]:
server.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[FilterLog]:
## filterOptions: settings for this filter.
## Returns a list of all logs matching a given filter object.
result = @[]
rpcsrv.rpc("eth_getWork") do() -> array[3, UInt256]:
server.rpc("eth_getWork") do() -> array[3, UInt256]:
## Returns the hash of the current block, the seedHash, and the boundary condition to be met ("target").
## Returned list has the following properties:
## DATA, 32 Bytes - current block header pow-hash.
@ -622,7 +627,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## DATA, 32 Bytes - the boundary condition ("target"), 2^256 / difficulty.
discard
rpcsrv.rpc("eth_submitWork") do(nonce: int64, powHash: HexDataStr, mixDigest: HexDataStr) -> bool:
server.rpc("eth_submitWork") do(nonce: int64, powHash: HexDataStr, mixDigest: HexDataStr) -> bool:
## Used for submitting a proof-of-work solution.
##
## nonce: the nonce found.
@ -631,12 +636,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
## Returns true if the provided solution is valid, otherwise false.
discard
rpcsrv.rpc("eth_submitHashrate") do(hashRate: HexDataStr, id: HexDataStr) -> bool:
server.rpc("eth_submitHashrate") do(hashRate: HexDataStr, id: HexDataStr) -> bool:
## Used for submitting mining hashrate.
##
## hashRate: a hexadecimal string representation (32 bytes) of the hash rate.
## id: a random hexadecimal(32 bytes) ID identifying the client.
## Returns true if submitting went through succesfully and false otherwise.
discard
discard]#

View File

@ -16,9 +16,9 @@ import
type
SyncState* = object
# Returned to user
startingBlock*: BlockNumber
currentBlock*: BlockNumber
highestBlock*: BlockNumber
startingBlock*: HexQuantityStr # BlockNumber
currentBlock* : HexQuantityStr # BlockNumber
highestBlock* : HexQuantityStr # BlockNumber
EthSend* = object
# Parameter from user

View File

@ -7,8 +7,8 @@
# This file may not be copied, modified, or distributed except according to
# those terms.
import hexstrings, eth/common, stew/byteutils,
../db/[db_chain], strutils,
import hexstrings, eth/[common, rlp], stew/byteutils,
../db/[db_chain], strutils, algorithm,
../constants, stint
func toAddress*(value: EthAddressStr): EthAddress = hexToPaddedByteArray[20](value.string)
@ -32,3 +32,20 @@ proc headerFromTag*(chain: BaseChainDB, blockTag: string): BlockHeader =
tag.validateHexQuantity
let blockNum = stint.fromHex(UInt256, tag)
result = chain.getBlockHeader(blockNum.toBlockNumber)
proc calculateMedianGasPrice*(chain: BaseChainDB): GasInt =
var prices = newSeqOfCap[GasInt](64)
let header = chain.getCanonicalHead()
for encodedTx in chain.getBlockTransactionData(header.txRoot):
let tx = rlp.decode(encodedTx, Transaction)
prices.add(tx.gasPrice)
if prices.len > 0:
sort(prices)
let middle = prices.len div 2
if prices.len mod 2 == 0:
# prevent overflow
let price = prices[middle].uint64 + prices[middle - 1].uint64
result = (price div 2).GasInt
else:
result = prices[middle]

View File

@ -50,7 +50,7 @@ proc captureAccount(n: JsonNode, db: AccountsCache, address: EthAddress, name: s
let codeHash = db.getCodeHash(address)
let storageRoot = db.getStorageRoot(address)
jaccount["nonce"] = %(encodeQuantity(nonce).toLowerAscii)
jaccount["nonce"] = %(encodeQuantity(nonce).string.toLowerAscii)
jaccount["balance"] = %("0x" & balance.toHex)
let code = db.getCode(address)

View File

@ -16,20 +16,20 @@ import
proc web3_clientVersion(): string
proc web3_sha3(data: string): string
proc net_version(): string
proc net_peerCount(): int
proc net_peerCount(): HexQuantityStr
proc net_listening(): bool
proc eth_protocolVersion(): string
proc eth_syncing(): JsonNode
proc eth_coinbase(): EthAddressStr
proc eth_mining(): bool
proc eth_hashrate(): int
proc eth_gasPrice(): GasInt
proc eth_hashrate(): HexQuantityStr
proc eth_gasPrice(): HexQuantityStr
proc eth_accounts(): seq[EthAddressStr]
proc eth_blockNumber(): BlockNumber
proc eth_getBalance(data: EthAddressStr, quantityTag: string): UInt256
proc eth_getStorageAt(data: EthAddressStr, quantity: int, quantityTag: string): seq[byte]
proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string)
proc eth_getBlockTransactionCountByHash(data: array[32, byte])
proc eth_blockNumber(): HexQuantityStr
proc eth_getBalance(data: EthAddressStr, quantityTag: string): HexQuantityStr
proc eth_getStorageAt(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string): seq[byte]
proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string): HexQuantityStr
proc eth_getBlockTransactionCountByHash(data: Hash256): HexQuantityStr
proc eth_getBlockTransactionCountByNumber(quantityTag: string)
proc eth_getUncleCountByBlockHash(data: array[32, byte])
proc eth_getUncleCountByBlockNumber(quantityTag: string)

View File

@ -6,7 +6,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
unittest, json, strformat, options, nimcrypto, stew/byteutils,
unittest, json, strformat, strutils, options, tables, nimcrypto, stew/byteutils,
json_rpc/[rpcserver, rpcclient], eth/common as eth_common,
eth/[rlp, keys], eth/trie/db, eth/p2p/rlpx_protocols/eth_protocol,
../nimbus/rpc/[common, p2p, hexstrings, rpc_types],
@ -18,7 +18,7 @@ import
from eth/p2p/rlpx_protocols/whisper_protocol import SymKey
# Perform checks for hex string validation
doHexStrTests()
#doHexStrTests()
from os import getCurrentDir, DirSep
from strutils import rsplit
@ -32,6 +32,7 @@ createRpcSigs(RpcSocketClient, sigPath)
proc toEthAddressStr(address: EthAddress): EthAddressStr =
result = ("0x" & address.toHex).ethAddressStr
proc doTests {.async.} =
# TODO: Include other transports such as Http
var ethNode = setupEthNode(eth)
@ -57,7 +58,7 @@ proc doTests {.async.} =
var
rpcServer = newRpcSocketServer(["localhost:" & $RPC_PORT])
client = newRpcSocketClient()
setupCommonRpc(rpcServer)
setupCommonRpc(ethNode, rpcServer)
setupEthRpc(ethNode, chain, rpcServer)
# Begin tests
@ -66,25 +67,122 @@ proc doTests {.async.} =
# TODO: add more tests here
suite "Remote Procedure Calls":
test "eth_call":
let
blockNum = state.blockheader.blockNumber
callParams = EthCall(value: some(100.u256))
r1 = await client.eth_call(callParams, "0x" & blockNum.toHex)
check r1 == "0x"
test "eth_getBalance":
let r2 = await client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0")
check r2 == 0
test "web3_clientVersion":
let res = await client.web3_clientVersion()
check res == NimbusIdent
let blockNum = state.blockheader.blockNumber
let r3 = await client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex)
check r3 == 0
test "eth_estimateGas":
let
call = EthCall()
blockNum = state.blockheader.blockNumber
r4 = await client.eth_estimateGas(call, "0x" & blockNum.toHex)
check r4 == 21_000
test "web3_sha3":
expect ValueError:
discard await client.web3_sha3(NimbusName)
let data = "0x" & byteutils.toHex(NimbusName.toOpenArrayByte(0, NimbusName.len-1))
let res = await client.web3_sha3(data)
let rawdata = nimcrypto.fromHex(data[2 .. ^1])
let hash = "0x" & $keccak_256.digest(rawdata)
check hash == res
test "net_version":
let res = await client.net_version()
check res == $conf.net.networkId
test "net_listening":
let res = await client.net_listening()
let listening = ethNode.peerPool.connectedNodes.len < conf.net.maxPeers
check res == listening
test "net_peerCount":
let res = await client.net_peerCount()
let peerCount = ethNode.peerPool.connectedNodes.len
check isValidHexQuantity res.string
check res == encodeQuantity(peerCount.uint)
test "eth_protocolVersion":
let res = await client.eth_protocolVersion()
check res == $eth_protocol.protocolVersion
test "eth_syncing":
let res = await client.eth_syncing()
if res.kind == JBool:
let syncing = ethNode.peerPool.connectedNodes.len > 0
check res.getBool() == syncing
else:
check res.kind == JObject
check chain.startingBlock == UInt256.fromHex(res["startingBlock"].getStr())
check chain.currentBlock == UInt256.fromHex(res["currentBlock"].getStr())
check chain.highestBlock == UInt256.fromHex(res["highestBlock"].getStr())
test "eth_coinbase":
let res = await client.eth_coinbase()
# currently we don't have miner
check isValidEthAddress(res.string)
check res == ethAddressStr(EthAddress.default)
test "eth_mining":
let res = await client.eth_mining()
# currently we don't have miner
check res == false
test "eth_hashrate":
let res = await client.eth_hashrate()
# currently we don't have miner
check res == encodeQuantity(0.uint)
test "eth_gasPrice":
let res = await client.eth_gasPrice()
# genesis block doesn't have any transaction
# to generate meaningful prices
check res.string == "0x0"
test "eth_accounts":
let res = await client.eth_accounts()
# we do not own any accounts, yet
check res.len == 0
test "eth_blockNumber":
let res = await client.eth_blockNumber()
check res.string == "0x0"
test "eth_getBalance":
let a = await client.eth_getBalance(ethAddressStr("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), "0x0")
check a.string == "0x1b1ae4d6e2ef5000000"
let b = await client.eth_getBalance(ethAddressStr("0xfff4bad596633479a2a29f9a8b3f78eefd07e6ee"), "0x0")
check b.string == "0x56bc75e2d63100000"
let c = await client.eth_getBalance(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
check c.string == "0x3635c9adc5dea00000"
test "eth_getStorageAt":
let res = await client.eth_getStorageAt(ethAddressStr("0xfff33a3bd36abdbd412707b8e310d6011454a7ae"), hexQuantityStr "0x0", "0x0")
check hexDataStr(0.u256).string == hexDataStr(res).string
test "eth_getTransactionCount":
let res = await client.eth_getTransactionCount(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
check res.string == "0x0"
test "eth_getBlockTransactionCountByHash":
let hash = chain.getBlockHash(0.toBlockNumber)
let res = await client.eth_getBlockTransactionCountByHash(hash)
check res.string == "0x0"
#test "eth_call":
# let
# blockNum = state.blockheader.blockNumber
# callParams = EthCall(value: some(100.u256))
# r1 = await client.eth_call(callParams, "0x" & blockNum.toHex)
# check r1 == "0x"
#test "eth_getBalance":
# let r2 = await client.eth_getBalance(ZERO_ADDRESS.toEthAddressStr, "0x0")
# check r2 == 0
#
# let blockNum = state.blockheader.blockNumber
# let r3 = await client.eth_getBalance(address.toEthAddressStr, "0x" & blockNum.toHex)
# check r3 == 0
#test "eth_estimateGas":
# let
# call = EthCall()
# blockNum = state.blockheader.blockNumber
# r4 = await client.eth_estimateGas(call, "0x" & blockNum.toHex)
# check r4 == 21_000
rpcServer.stop()
rpcServer.close()