From f987e865624255235c948ee5584b75f6060503c0 Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 30 Jul 2020 14:21:11 +0700 Subject: [PATCH] implement more eth rpc --- nimbus/db/db_chain.nim | 46 +++++-- nimbus/rpc/hexstrings.nim | 11 +- nimbus/rpc/p2p.nim | 205 +++++++++++--------------------- nimbus/rpc/rpc_types.nim | 53 +++++---- nimbus/rpc/rpc_utils.nim | 89 +++++++++++++- tests/rpcclient/ethcallsigs.nim | 17 ++- tests/test_rpc.nim | 87 +++++++++++++- 7 files changed, 321 insertions(+), 187 deletions(-) diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index bc55cc0b6..c640b2588 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -24,10 +24,6 @@ type currentBlock*: BlockNumber highestBlock*: BlockNumber - #KeyType = enum - # blockNumberToHash - # blockHashToScore - # TransactionKey = tuple blockNumber: BlockNumber index: int @@ -132,7 +128,7 @@ proc addBlockNumberToHashLookup*(self: BaseChainDB; header: BlockHeader) = self.db.put(blockNumberToHashKey(header.blockNumber).toOpenArray, rlp.encode(header.hash)) -proc persistTransactions*(self: BaseChainDB, blockNumber: +proc persistTransactions*(self: BaseChainDB, blockNumber: BlockNumber, transactions: openArray[Transaction]): Hash256 = var trie = initHexaryTrie(self.db) for idx, tx in transactions: @@ -142,7 +138,14 @@ proc persistTransactions*(self: BaseChainDB, blockNumber: txKey: TransactionKey = (blockNumber, idx) trie.put(rlp.encode(idx), encodedTx) self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey)) - trie.rootHash + trie.rootHash + +proc getTransaction*(self: BaseChainDB, txRoot: Hash256, txIndex: int, res: var Transaction): bool = + var db = initHexaryTrie(self.db, txRoot) + let txData = db.get(rlp.encode(txIndex)) + if txData.len > 0: + res = rlp.decode(txData, Transaction) + result = true iterator getBlockTransactionData*(self: BaseChainDB, transactionRoot: Hash256): seq[byte] = var transactionDb = initHexaryTrie(self.db, transactionRoot) @@ -155,7 +158,11 @@ iterator getBlockTransactionData*(self: BaseChainDB, transactionRoot: Hash256): break inc transactionIdx -iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): Hash256 = +iterator getBlockTransactions*(self: BaseChainDB, header: BlockHeader): Transaction = + for encodedTx in self.getBlockTransactionData(header.txRoot): + yield rlp.decode(encodedTx, Transaction) + +iterator getBlockTransactionHashes*(self: BaseChainDB, blockHeader: BlockHeader): Hash256 = ## Returns an iterable of the transaction hashes from th block specified ## by the given block header. for encodedTx in self.getBlockTransactionData(blockHeader.txRoot): @@ -177,6 +184,12 @@ proc getUnclesCount*(self: BaseChainDB, ommersHash: Hash256): int = let r = rlpFromBytes(encodedUncles) result = r.listLen +proc getUncles*(self: BaseChainDB, ommersHash: Hash256): seq[BlockHeader] = + if ommersHash != EMPTY_UNCLE_HASH: + let encodedUncles = self.db.get(genericHashKey(ommersHash).toOpenArray) + if encodedUncles.len != 0: + result = rlp.decode(encodedUncles, seq[BlockHeader]) + proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool = var header: BlockHeader if self.getBlockHeader(blockHash, header): @@ -203,11 +216,22 @@ proc getUncleHashes*(self: BaseChainDB, blockHashes: openArray[Hash256]): seq[Ha for uncle in blockBody.uncles: result.add uncle.hash +proc getUncleHashes*(self: BaseChainDB, header: BlockHeader): seq[Hash256] = + if header.ommersHash != EMPTY_UNCLE_HASH: + let encodedUncles = self.db.get(genericHashKey(header.ommersHash).toOpenArray) + if encodedUncles.len != 0: + let uncles = rlp.decode(encodedUncles, seq[BlockHeader]) + for x in uncles: + result.add x.hash + proc getTransactionKey*(self: BaseChainDB, transactionHash: Hash256): tuple[blockNumber: BlockNumber, index: int] {.inline.} = - let - tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray) - key = rlp.decode(tx, TransactionKey) - return (key.blockNumber, key.index) + let tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray) + + if tx.len > 0: + let key = rlp.decode(tx, TransactionKey) + result = (key.blockNumber, key.index) + else: + result = (0.toBlockNumber, -1) proc removeTransactionFromCanonicalChain(self: BaseChainDB, transactionHash: Hash256) {.inline.} = ## Removes the transaction specified by the given hash from the canonical chain. diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index cd0905027..bebd1a70c 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -231,7 +231,6 @@ proc `%`*(value: whisper_protocol.Topic): JsonNode = proc `%`*(value: seq[byte]): JsonNode = result = %("0x" & value.toHex) - # Helpers for the fromJson procs proc toPublicKey*(key: string): PublicKey {.inline.} = @@ -271,6 +270,13 @@ proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = raise newException(ValueError, invalidMsg(argName) & "\" as an Ethereum address \"" & hexStr & "\"") result = hexStr.EthAddressStr +proc fromJson*(n: JsonNode, argName: string, result: var EthAddress) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidEthAddress: + raise newException(ValueError, invalidMsg(argName) & "\" as an Ethereum address \"" & hexStr & "\"") + hexToByteArray(hexStr, result) + proc fromJson*(n: JsonNode, argName: string, result: var EthHashStr) = n.kind.expect(JString, argName) let hexStr = n.getStr() @@ -336,3 +342,6 @@ proc fromJson*(n: JsonNode, argName: string, result: var Hash256) = if not hexStr.isValidHash256: raise newException(ValueError, invalidMsg(argName) & " as a Hash256 \"" & hexStr & "\"") hexToByteArray(hexStr, result.data) + +proc fromJson*(n: JsonNode, argName: string, result: var JsonNode) = + result = n diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index fdd389e25..27118014a 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -26,10 +26,6 @@ import type cast to avoid extra processing. ]# -# Work around for https://github.com/nim-lang/Nim/issues/8645 -# proc `%`*(value: Time): JsonNode = -# result = %value.toUnix - proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = proc getAccountDb(header: BlockHeader): ReadOnlyStateDB = @@ -280,49 +276,18 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = callData = callData(call, false, chain) result = estimateGas(callData, header, chain, call.gas.isSome) -#[ - func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject = - result.number = some(header.blockNumber) - result.hash = some(header.hash) - result.parentHash = header.parentHash - result.nonce = header.nonce.toUint - - # Calculate hash for all uncle headers - var - rawdata = newSeq[byte](blockBody.uncles.len * 32) - startIdx = 0 - for i in 0 ..< blockBody.uncles.len: - rawData[startIdx .. startIdx + 32] = blockBody.uncles[i].hash.data - startIdx += 32 - result.sha3Uncles = keccakHash(rawData) - - result.logsBloom = some(header.bloom) - result.transactionsRoot = header.txRoot - result.stateRoot = header.stateRoot - result.receiptsRoot = header.receiptRoot - result.miner = ZERO_ADDRESS # TODO: Get miner address - result.difficulty = header.difficulty - result.totalDifficulty = header.difficulty # TODO: Calculate - result.extraData = header.extraData - result.size = 0 # TODO: Calculate block size - result.gasLimit = header.gasLimit - result.gasUsed = header.gasUsed - result.timestamp = header.timeStamp - result.transactions = blockBody.transactions - result.uncles = @[] - for i in 0 ..< blockBody.uncles.len: - result.uncles[i] = blockBody.uncles[i].hash - server.rpc("eth_getBlockByHash") do(data: EthHashStr, fullTransactions: bool) -> Option[BlockObject]: ## Returns information about a block by hash. ## ## data: Hash of a block. ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. ## Returns BlockObject or nil when no block was found. - let - h = data.toHash - header = chain.getBlockHeader(h) - result = some(populateBlockObject(header, getBlockBody(h))) + var + header: BlockHeader + hash = data.toHash + + if chain.getBlockHeader(hash, header): + result = some(populateBlockObject(header, chain, fullTransactions)) server.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> Option[BlockObject]: ## Returns information about a block by block number. @@ -330,110 +295,72 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = ## quantityTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter. ## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions. ## Returns BlockObject or nil when no block was found. - let - header = chain.headerFromTag(quantityTag) + try: + let header = chain.headerFromTag(quantityTag) + result = some(populateBlockObject(header, chain, fullTransactions)) + except: + result = none(BlockObject) - result = some(populateBlockObject(header, getBlockBody(header.hash))) - - proc populateTransactionObject(transaction: Transaction, txIndex: int64, blockHeader: BlockHeader, blockHash: Hash256): TransactionObject = - let - # TODO: header.stateRoot to prevStateRoot? - vmState = newBaseVMState(blockHeader.stateRoot, blockHeader, chain) - accountDb = vmState.readOnlyStateDB() - address = transaction.getSender() - txCount = accountDb.getNonce(address) - txHash = transaction.rlpHash - accountGas = accountDb.balance(address) - - result.hash = txHash - result.nonce = txCount - result.blockHash = some(blockHash) - result.blockNumber = some(blockHeader.blockNumber) - result.transactionIndex = some(txIndex) - result.source = transaction.getSender() - result.to = some(transaction.to) - result.value = transaction.value - result.gasPrice = transaction.gasPrice - result.gas = accountGas - result.input = transaction.payload - - server.rpc("eth_getTransactionByHash") do(data: EthHashStr) -> TransactionObject: + server.rpc("eth_getTransactionByHash") do(data: EthHashStr) -> Option[TransactionObject]: ## Returns the information about a transaction requested by transaction hash. ## ## data: hash of a transaction. ## Returns requested transaction information. - let - h = data.toHash() - txDetails = chain.getTransactionKey(h) - header = chain.getBlockHeader(txDetails.blockNumber) - blockHash = chain.getBlockHash(txDetails.blockNumber) - transaction = getBlockBody(blockHash).transactions[txDetails.index] - result = populateTransactionObject(transaction, txDetails.index, header, blockHash) + let txDetails = chain.getTransactionKey(data.toHash()) + if txDetails.index < 0: + return none(TransactionObject) + + let header = chain.getBlockHeader(txDetails.blockNumber) + var tx: Transaction + if chain.getTransaction(header.txRoot, txDetails.index, tx): + result = some(populateTransactionObject(tx, header, txDetails.index)) + # TODO: if the requested transaction not in blockchain # try to look for pending transaction in txpool - server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> TransactionObject: + server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: EthHashStr, quantity: HexQuantityStr) -> Option[TransactionObject]: ## Returns information about a transaction by block hash and transaction index position. ## ## data: hash of a block. ## quantity: integer of the transaction index position. ## Returns requested transaction information. - let - blockHash = data.toHash() - header = chain.getBlockHeader(blockHash) - transaction = getBlockBody(blockHash).transactions[quantity] - result = populateTransactionObject(transaction, quantity, header, blockHash) + let index = hexToInt(quantity.string, int) + var header: BlockHeader + if not chain.getBlockHeader(data.toHash(), header): + return none(TransactionObject) - server.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> TransactionObject: + var tx: Transaction + if chain.getTransaction(header.txRoot, index, tx): + result = some(populateTransactionObject(tx, header, index)) + + server.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: HexQuantityStr) -> Option[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. ## quantity: the transaction index position. let header = chain.headerFromTag(quantityTag) - blockHash = header.hash - transaction = getBlockBody(blockHash).transactions[quantity] - result = populateTransactionObject(transaction, quantity, header, blockHash) + index = hexToInt(quantity.string, int) - proc populateReceipt(receipt: Receipt, gasUsed: GasInt, tx: Transaction, txIndex: int, blockHeader: BlockHeader): ReceiptObject = - result.transactionHash = tx.rlpHash - result.transactionIndex = txIndex - result.blockHash = blockHeader.hash - result.blockNumber = blockHeader.blockNumber - result.sender = tx.getSender() - result.to = some(tx.to) - result.cumulativeGasUsed = receipt.cumulativeGasUsed - result.gasUsed = gasUsed + var tx: Transaction + if chain.getTransaction(header.txRoot, index, tx): + result = some(populateTransactionObject(tx, header, index)) - if tx.isContractCreation: - var sender: EthAddress - if tx.getSender(sender): - let contractAddress = generateAddress(sender, tx.accountNonce) - result.contractAddress = some(contractAddress) - else: - doAssert(false) - else: - result.contractAddress = none(EthAddress) - - result.logs = receipt.logs - result.logsBloom = receipt.bloom - # post-transaction stateroot (pre Byzantium). - if receipt.hasStateRoot: - result.root = some(receipt.stateRoot) - else: - # 1 = success, 0 = failure. - result.status = some(receipt.status) - - server.rpc("eth_getTransactionReceipt") do(data: EthHashStr) -> ReceiptObject: + server.rpc("eth_getTransactionReceipt") do(data: EthHashStr) -> Option[ReceiptObject]: ## Returns the receipt of a transaction by transaction hash. ## ## data: hash of a transaction. ## Returns transaction receipt. - let - h = data.toHash - txDetails = chain.getTransactionKey(h) - header = chain.getBlockHeader(txDetails.blockNumber) - body = getBlockBody(header.hash) + + let txDetails = chain.getTransactionKey(data.toHash()) + if txDetails.index < 0: + return none(ReceiptObject) + + let header = chain.getBlockHeader(txDetails.blockNumber) + var tx: Transaction + if not chain.getTransaction(header.txRoot, txDetails.index, tx): + return none(ReceiptObject) + var idx = 0 prevGasUsed = GasInt(0) @@ -442,37 +369,47 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) = let gasUsed = receipt.cumulativeGasUsed - prevGasUsed prevGasUsed = receipt.cumulativeGasUsed if idx == txDetails.index: - return populateReceipt(receipt, gasUsed, body.transactions[txDetails.index], txDetails.index, header) + return some(populateReceipt(receipt, gasUsed, tx, txDetails.index, header)) idx.inc - server.rpc("eth_getUncleByBlockHashAndIndex") do(data: EthHashStr, quantity: int) -> Option[BlockObject]: + server.rpc("eth_getUncleByBlockHashAndIndex") do(data: EthHashStr, quantity: HexQuantityStr) -> Option[BlockObject]: ## Returns information about a uncle of a block by hash and uncle index position. ## ## data: hash of block. ## quantity: the uncle's index position. ## Returns BlockObject or nil when no block was found. - let - blockHash = data.toHash() - body = getBlockBody(blockHash) - if quantity < 0 or quantity >= body.uncles.len: - raise newException(ValueError, "Uncle index out of range") - let uncle = body.uncles[quantity] - result = some(populateBlockObject(uncle, body)) + let index = hexToInt(quantity.string, int) + var header: BlockHeader + if not chain.getBlockHeader(data.toHash(), header): + return none(BlockObject) - server.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> Option[BlockObject]: + let uncles = chain.getUncles(header.ommersHash) + if index < 0 or index >= uncles.len: + return none(BlockObject) + + var uncle = populateBlockObject(uncles[index], chain, false, true) + uncle.totalDifficulty = encodeQuantity(chain.getScore(header.hash)) + result = some(uncle) + + server.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: HexQuantityStr) -> 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. ## quantity: the uncle's index position. ## Returns BlockObject or nil when no block was found. let + index = hexToInt(quantity.string, int) header = chain.headerFromTag(quantityTag) - body = getBlockBody(header.hash) - if quantity < 0 or quantity >= body.uncles.len: - raise newException(ValueError, "Uncle index out of range") - let uncle = body.uncles[quantity] - result = some(populateBlockObject(uncle, body)) + uncles = chain.getUncles(header.ommersHash) + if index < 0 or index >= uncles.len: + return none(BlockObject) + + var uncle = populateBlockObject(uncles[index], chain, false, true) + uncle.totalDifficulty = encodeQuantity(chain.getScore(header.hash)) + result = some(uncle) + +#[ 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. diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index 231c423fa..fd41d3914 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -1,5 +1,5 @@ import - hexstrings, options, eth/[common, keys, rlp], + hexstrings, options, eth/[common, keys, rlp], json, eth/p2p/rlpx_protocols/whisper_protocol #[ @@ -43,39 +43,42 @@ type ## Note that this includes slightly different information from eth/common.BlockHeader BlockObject* = object # Returned to user - number*: Option[BlockNumber] # the block number. null when its pending block. + number*: Option[HexQuantityStr] # the block number. null when its pending block. hash*: Option[Hash256] # hash of the block. null when its pending block. parentHash*: Hash256 # hash of the parent block. - nonce*: uint64 # hash of the generated proof-of-work. null when its pending block. + nonce*: Option[HexDataStr] # hash of the generated proof-of-work. null when its pending block. sha3Uncles*: Hash256 # SHA3 of the uncles data in the block. logsBloom*: Option[BloomFilter] # the bloom filter for the logs of the block. null when its pending block. transactionsRoot*: Hash256 # the root of the transaction trie of the block. stateRoot*: Hash256 # the root of the final state trie of the block. receiptsRoot*: Hash256 # the root of the receipts trie of the block. miner*: EthAddress # the address of the beneficiary to whom the mining rewards were given. - difficulty*: UInt256 # integer of the difficulty for this block. - totalDifficulty*: UInt256 # integer of the total difficulty of the chain until this block. - extraData*: Blob # the "extra data" field of this block. - size*: int # integer the size of this block in bytes. - gasLimit*: GasInt # the maximum gas allowed in this block. - gasUsed*: GasInt # the total used gas by all transactions in this block. - timestamp*: EthTime # the unix timestamp for when the block was collated. - transactions*: seq[Transaction] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. + difficulty*: HexQuantityStr # integer of the difficulty for this block. + totalDifficulty*: HexQuantityStr# integer of the total difficulty of the chain until this block. + extraData*: HexDataStr # the "extra data" field of this block. + size*: HexQuantityStr # integer the size of this block in bytes. + gasLimit*: HexQuantityStr # the maximum gas allowed in this block. + gasUsed*: HexQuantityStr # the total used gas by all transactions in this block. + timestamp*: HexQuantityStr # the unix timestamp for when the block was collated. + transactions*: seq[JsonNode] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter. uncles*: seq[Hash256] # list of uncle hashes. TransactionObject* = object # A transaction object, or null when no transaction was found: # Returned to user - hash*: Hash256 # hash of the transaction. - nonce*: AccountNonce # the number of transactions made by the sender prior to this one. blockHash*: Option[Hash256] # hash of the block where this transaction was in. null when its pending. - blockNumber*: Option[BlockNumber] # block number where this transaction was in. null when its pending. - transactionIndex*: Option[int64] # integer of the transactions index position in the block. null when its pending. - source*: EthAddress # address of the sender. - to*: Option[EthAddress] # address of the receiver. null when its a contract creation transaction. - value*: UInt256 # value transferred in Wei. - gasPrice*: GasInt # gas price provided by the sender in Wei. - gas*: GasInt # gas provided by the sender. + blockNumber*: Option[HexQuantityStr] # block number where this transaction was in. null when its pending. + `from`*: EthAddress # address of the sender. + gas*: HexQuantityStr # gas provided by the sender. + gasPrice*: HexQuantityStr # gas price provided by the sender in Wei. + hash*: Hash256 # hash of the transaction. input*: Blob # the data send along with the transaction. + nonce*: HexQuantityStr # the number of transactions made by the sender prior to this one. + to*: Option[EthAddress] # address of the receiver. null when its a contract creation transaction. + transactionIndex*: Option[HexQuantityStr] # integer of the transactions index position in the block. null when its pending. + value*: HexQuantityStr # value transferred in Wei. + v*: HexQuantityStr # ECDSA recovery id + r*: HexQuantityStr # 32 Bytes - ECDSA signature r + s*: HexQuantityStr # 32 Bytes - ECDSA signature s FilterLog* = object # Returned to user @@ -94,13 +97,13 @@ type ReceiptObject* = object # A transaction receipt object, or null when no receipt was found: transactionHash*: Hash256 # hash of the transaction. - transactionIndex*: int # integer of the transactions index position in the block. + transactionIndex*: HexQuantityStr # integer of the transactions index position in the block. blockHash*: Hash256 # hash of the block where this transaction was in. - blockNumber*: BlockNumber # block number where this transaction was in. - sender*: EthAddress # address of the sender. + blockNumber*: HexQuantityStr # block number where this transaction was in. + `from`*: EthAddress # address of the sender. to*: Option[EthAddress] # address of the receiver. null when its a contract creation transaction. - cumulativeGasUsed*: GasInt # the total amount of gas used when this transaction was executed in the block. - gasUsed*: GasInt # the amount of gas used by this specific transaction alone. + cumulativeGasUsed*: HexQuantityStr # the total amount of gas used when this transaction was executed in the block. + gasUsed*: HexQuantityStr # the amount of gas used by this specific transaction alone. contractAddress*: Option[EthAddress] # the contract address created, if the transaction was a contract creation, otherwise null. logs*: seq[Log] # list of log objects which this transaction generated. logsBloom*: BloomFilter # bloom filter for light clients to quickly retrieve related logs. diff --git a/nimbus/rpc/rpc_utils.nim b/nimbus/rpc/rpc_utils.nim index bf1918f67..b1dec6a7f 100644 --- a/nimbus/rpc/rpc_utils.nim +++ b/nimbus/rpc/rpc_utils.nim @@ -8,10 +8,10 @@ # those terms. import hexstrings, eth/[common, rlp, keys, trie/db], stew/byteutils, nimcrypto, - ../db/[db_chain, accounts_cache], strutils, algorithm, options, + ../db/[db_chain, accounts_cache], strutils, algorithm, options, times, json, ../constants, stint, hexstrings, rpc_types, ../config, ../vm_state_transactions, ../vm_state, ../vm_types, ../vm/interpreter/vm_forks, - ../vm/computation, ../p2p/executor + ../vm/computation, ../p2p/executor, ../utils, ../transaction type UnsignedTx* = object @@ -242,4 +242,87 @@ proc estimateGas*(call: CallData, header: BlockHeader, chain: BaseChainDB, haveG let gasUsed = processTransaction(tx, call.source, vmState, fork) result = encodeQuantity(gasUsed.uint64) dbTx.dispose() - # TODO: handle revert and error \ No newline at end of file + # TODO: handle revert and error + +proc populateTransactionObject*(tx: Transaction, header: BlockHeader, txIndex: int): TransactionObject = + result.blockHash = some(header.hash) + result.blockNumber = some(encodeQuantity(header.blockNumber)) + result.`from` = tx.getSender() + result.gas = encodeQuantity(tx.gasLimit.uint64) + result.gasPrice = encodeQuantity(tx.gasPrice.uint64) + result.hash = tx.rlpHash + result.input = tx.payLoad + result.nonce = encodeQuantity(tx.accountNonce.uint64) + if not tx.isContractCreation: + result.to = some(tx.to) + result.transactionIndex = some(encodeQuantity(txIndex.uint64)) + result.value = encodeQuantity(tx.value) + result.v = encodeQuantity(tx.V.uint) + result.r = encodeQuantity(tx.R) + result.s = encodeQuantity(tx.S) + +proc populateBlockObject*(header: BlockHeader, chain: BaseChainDB, fullTx: bool, isUncle = false): BlockObject = + let blockHash = header.blockHash + + result.number = some(encodeQuantity(header.blockNumber)) + result.hash = some(blockHash) + result.parentHash = header.parentHash + result.nonce = some(hexDataStr(header.nonce)) + result.sha3Uncles = header.ommersHash + result.logsBloom = some(header.bloom) + result.transactionsRoot = header.txRoot + result.stateRoot = header.stateRoot + result.receiptsRoot = header.receiptRoot + result.miner = header.coinbase + result.difficulty = encodeQuantity(header.difficulty) + result.extraData = hexDataStr(header.extraData) + + # discard sizeof(seq[byte]) of extraData and use actual length + let size = sizeof(BlockHeader) - sizeof(Blob) + header.extraData.len + result.size = encodeQuantity(size.uint) + + result.gasLimit = encodeQuantity(header.gasLimit.uint64) + result.gasUsed = encodeQuantity(header.gasUsed.uint64) + result.timestamp = encodeQuantity(header.timeStamp.toUnix.uint64) + + if not isUncle: + result.totalDifficulty = encodeQuantity(chain.getScore(blockHash)) + result.uncles = chain.getUncleHashes(header) + + if fullTx: + var i = 0 + for tx in chain.getBlockTransactions(header): + result.transactions.add %(populateTransactionObject(tx, header, i)) + inc i + else: + for x in chain.getBlockTransactionHashes(header): + result.transactions.add %(x) + +proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction, txIndex: int, header: BlockHeader): ReceiptObject = + result.transactionHash = tx.rlpHash + result.transactionIndex = encodeQuantity(txIndex.uint) + result.blockHash = header.hash + result.blockNumber = encodeQuantity(header.blockNumber) + result.`from` = tx.getSender() + + if tx.isContractCreation: + result.to = some(tx.to) + + result.cumulativeGasUsed = encodeQuantity(receipt.cumulativeGasUsed.uint64) + result.gasUsed = encodeQuantity(gasUsed.uint64) + + if tx.isContractCreation: + var sender: EthAddress + if tx.getSender(sender): + let contractAddress = generateAddress(sender, tx.accountNonce) + result.contractAddress = some(contractAddress) + + result.logs = receipt.logs + result.logsBloom = receipt.bloom + + # post-transaction stateroot (pre Byzantium). + if receipt.hasStateRoot: + result.root = some(receipt.stateRoot) + else: + # 1 = success, 0 = failure. + result.status = some(receipt.status) diff --git a/tests/rpcclient/ethcallsigs.nim b/tests/rpcclient/ethcallsigs.nim index 3186487ec..0a7728802 100644 --- a/tests/rpcclient/ethcallsigs.nim +++ b/tests/rpcclient/ethcallsigs.nim @@ -40,15 +40,14 @@ proc eth_sendTransaction(data: TxSend): EthHashStr proc eth_sendRawTransaction(data: HexDataStr): EthHashStr proc eth_call(call: EthCall, quantityTag: string): HexDataStr proc eth_estimateGas(call: EthCall, quantityTag: string): HexQuantityStr - -proc eth_getBlockByHash(data: Hash256, fullTransactions: bool): BlockObject -proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject -proc eth_getTransactionByHash(data: Hash256): TransactionObject -proc eth_getTransactionByBlockHashAndIndex(data: Hash256, quantity: int): TransactionObject -proc eth_getTransactionByBlockNumberAndIndex(quantityTag: string, quantity: int): TransactionObject -proc eth_getTransactionReceipt(data: Hash256): ReceiptObject -proc eth_getUncleByBlockHashAndIndex(data: Hash256, quantity: int64): BlockObject -proc eth_getUncleByBlockNumberAndIndex(quantityTag: string, quantity: int64): BlockObject +proc eth_getBlockByHash(data: Hash256, fullTransactions: bool): Option[BlockObject] +proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): Option[BlockObject] +proc eth_getTransactionByHash(data: Hash256): Option[TransactionObject] +proc eth_getTransactionByBlockHashAndIndex(data: Hash256, quantity: HexQuantityStr): Option[TransactionObject] +proc eth_getTransactionByBlockNumberAndIndex(quantityTag: string, quantity: HexQuantityStr): Option[TransactionObject] +proc eth_getTransactionReceipt(data: Hash256): Option[ReceiptObject] +proc eth_getUncleByBlockHashAndIndex(data: Hash256, quantity: HexQuantityStr): Option[BlockObject] +proc eth_getUncleByBlockNumberAndIndex(quantityTag: string, quantity: HexQuantityStr): Option[BlockObject] #[ proc eth_getCompilers(): seq[string] diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index 932774418..cbf62d429 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -30,7 +30,12 @@ template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0] const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim" createRpcSigs(RpcSocketClient, sigPath) -proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfiguration) = +type + TestEnv = object + txHash: Hash256 + blockHash: HAsh256 + +proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfiguration): TestEnv = var parent = chain.getCanonicalHead() ac = newAccountStateDB(chain.db, parent.stateRoot, chain.pruneTrie) @@ -84,9 +89,8 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura timeStamp = date.toTime difficulty = calcDifficulty(chain.config, timeStamp, parent) - let header = BlockHeader( + var header = BlockHeader( parentHash : parentHash, - #ommersHash*: Hash256 #coinbase*: EthAddress stateRoot : vmState.accountDb.rootHash, txRoot : txRoot, @@ -102,7 +106,15 @@ proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfigura #nonce: BlockNonce ) + let uncles = [header] + header.ommersHash = chain.persistUncles(uncles) + discard chain.persistHeaderToDb(header) + result = TestEnv( + txHash: signedTx1.rlpHash, + blockHash: header.hash + ) + proc doTests {.async.} = # TODO: Include other transports such as Http @@ -131,7 +143,7 @@ proc doTests {.async.} = defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain) doAssert(canonicalHeadHashKey().toOpenArray in chain.db) - setupEnv(chain, signer, ks2, conf) + let env = setupEnv(chain, signer, ks2, conf) # Create Ethereum RPCs let RPC_PORT = 8545 @@ -321,6 +333,73 @@ proc doTests {.async.} = let res = await client.eth_estimateGas(ec, "latest") check hexToInt(res.string, int) == 21000 + test "eth_getBlockByHash": + let res = await client.eth_getBlockByHash(env.blockHash, true) + check res.isSome + check res.get().hash.get() == env.blockHash + let res2 = await client.eth_getBlockByHash(env.txHash, true) + check res2.isNone + + test "eth_getBlockByNumber": + let res = await client.eth_getBlockByNumber("latest", true) + check res.isSome + check res.get().hash.get() == env.blockHash + let res2 = await client.eth_getBlockByNumber($1, true) + check res2.isNone + + test "eth_getTransactionByHash": + let res = await client.eth_getTransactionByHash(env.txHash) + check res.isSome + check res.get().blockNumber.get().string.hexToInt(int) == 1 + let res2 = await client.eth_getTransactionByHash(env.blockHash) + check res2.isNone + + test "eth_getTransactionByBlockHashAndIndex": + let res = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, encodeQuantity(0)) + check res.isSome + check res.get().blockNumber.get().string.hexToInt(int) == 1 + + let res2 = await client.eth_getTransactionByBlockHashAndIndex(env.blockHash, encodeQuantity(3)) + check res2.isNone + + let res3 = await client.eth_getTransactionByBlockHashAndIndex(env.txHash, encodeQuantity(3)) + check res3.isNone + + test "eth_getTransactionByBlockNumberAndIndex": + let res = await client.eth_getTransactionByBlockNumberAndIndex("latest", encodeQuantity(1)) + check res.isSome + check res.get().blockNumber.get().string.hexToInt(int) == 1 + + let res2 = await client.eth_getTransactionByBlockNumberAndIndex("latest", encodeQuantity(3)) + check res2.isNone + + test "eth_getTransactionReceipt": + let res = await client.eth_getTransactionReceipt(env.txHash) + check res.isSome + check res.get().blockNumber.string.hexToInt(int) == 1 + + let res2 = await client.eth_getTransactionReceipt(env.blockHash) + check res2.isNone + + test "eth_getUncleByBlockHashAndIndex": + let res = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, encodeQuantity(0)) + check res.isSome + check res.get().number.get().string.hexToInt(int) == 1 + + let res2 = await client.eth_getUncleByBlockHashAndIndex(env.blockHash, encodeQuantity(1)) + check res2.isNone + + let res3 = await client.eth_getUncleByBlockHashAndIndex(env.txHash, encodeQuantity(0)) + check res3.isNone + + test "eth_getUncleByBlockNumberAndIndex": + let res = await client.eth_getUncleByBlockNumberAndIndex("latest", encodeQuantity(0)) + check res.isSome + check res.get().number.get().string.hexToInt(int) == 1 + + let res2 = await client.eth_getUncleByBlockNumberAndIndex("latest", encodeQuantity(1)) + check res2.isNone + rpcServer.stop() rpcServer.close()