diff --git a/nimbus/db/db_chain.nim b/nimbus/db/db_chain.nim index a1d79ac52..6dc6115ec 100644 --- a/nimbus/db/db_chain.nim +++ b/nimbus/db/db_chain.nim @@ -113,6 +113,11 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader): # for encoded_transaction in all_encoded_transactions: # yield keccak(encoded_transaction) +proc getTransactionKey*(self: BaseChainDB, transactionHash: Hash256): tuple[blockNumber: BlockNumber, index: int] {.inline.} = + let + tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray).toRange + key = rlp.decode(tx, TransactionKey) + return (key.blockNumber, key.index) 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 7e411aff9..9961d1265 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -10,6 +10,17 @@ ## This module implements the Ethereum hexadecimal string formats for JSON ## See: https://github.com/ethereum/wiki/wiki/JSON-RPC#hex-value-encoding +#[ + Note: + The following types are converted to hex strings when marshalled to JSON: + * EthAddress + * ref EthAddress + * Hash256 + * UInt256 + * openArray[seq] + * ref BloomFilter +]# + import eth_common/eth_types, stint, byteutils, nimcrypto type @@ -142,6 +153,9 @@ proc `%`*(value: EthHashStr): JsonNode = proc `%`*(value: EthAddress): JsonNode = result = %("0x" & value.toHex) +proc `%`*(value: ref EthAddress): JsonNode = + result = %("0x" & value[].toHex) + proc `%`*(value: Hash256): JsonNode = result = %("0x" & $value) diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index e716d66d6..22a57e454 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -9,8 +9,8 @@ import nimcrypto, json_rpc/rpcserver, eth_p2p, hexstrings, strutils, stint, ../config, ../vm_state, ../constants, eth_trie/[memdb, types], - ../db/[db_chain, state_db], eth_common, rpc_types, byteutils, - ranges/typedranges, times, ../utils/header + ../db/[db_chain, state_db, storage_types], eth_common, rpc_types, byteutils, + ranges/typedranges, times, ../utils/header, rlp #[ Note: @@ -27,11 +27,17 @@ proc `%`*(value: Time): JsonNode = func strToAddress(value: string): EthAddress = hexToPaddedByteArray[20](value) +func toHash(value: array[32, byte]): Hash256 {.inline.} = + result.data = value + +func strToHash(value: string): Hash256 {.inline.} = + result = hexToPaddedByteArray[32](value).toHash + func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let tag = blockTag.toLowerAscii case tag of "latest": result = chain.getCanonicalHead() - of "earliest": result = chain.getCanonicalBlockHeaderByNumber(GENESIS_BLOCK_NUMBER) + of "earliest": result = chain.getBlockHeader(GENESIS_BLOCK_NUMBER) of "pending": #TODO: Implement get pending block raise newException(ValueError, "Pending tag not yet implemented") @@ -39,13 +45,12 @@ func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = # Raises are trapped and wrapped in JSON when returned to the user. tag.validateHexQuantity let blockNum = stint.fromHex(UInt256, tag) - result = chain.getCanonicalBlockHeaderByNumber(blockNum) + result = chain.getBlockHeader(blockNum) proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = template chain: untyped = BaseChainDB(node.chain) # TODO: Sensible casting proc accountDbFromTag(tag: string, readOnly = true): AccountStateDb = - # Note: This is a read only account let header = chain.headerFromTag(tag) vmState = newBaseVMState(header, chain) @@ -102,9 +107,9 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns integer of the current balance in wei. let - account_db = accountDbFromTag(quantityTag) + accountDb = accountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) - balance = account_db.get_balance(addrBytes) + balance = accountDb.get_balance(addrBytes) result = balance.toInt @@ -116,31 +121,32 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns: the value at this storage position. let - account_db = accountDbFromTag(quantityTag) + accountDb = accountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) - storage = account_db.getStorage(addrBytes, quantity.u256) + storage = accountDb.getStorage(addrBytes, quantity.u256) if storage[1]: result = storage[0] - rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> int: + rpcsrv.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> UInt256: ## 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 - header = chain.headerFromTag(quantityTag) - body = chain.getBlockBody(header.hash) - result = body.transactions.len + addrBytes = data.string.strToAddress() + accountDb = accountDbFromTag(quantityTag) + result = accountDb.getNonce(addrBytes) rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: ## 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: Hash256 - hashData.data = hexToPaddedByteArray[32](data.string) + var hashData = strToHash(data.string) let body = chain.getBlockBody(hashData) + if body == nil: + raise newException(ValueError, "Cannot find hash") result = body.transactions.len rpcsrv.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int: @@ -150,7 +156,10 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns integer of the number of transactions in this block. let header = chain.headerFromTag(quantityTag) + accountDb = accountDbFromTag(quantityTag) body = chain.getBlockBody(header.hash) + if body == nil: + raise newException(ValueError, "Cannot find hash") result = body.transactions.len rpcsrv.rpc("eth_getUncleCountByBlockHash") do(data: HexDataStr) -> int: @@ -158,9 +167,10 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## ## data: hash of a block. ## Returns integer of the number of uncles in this block. - var hashData: Hash256 - hashData.data = hexToPaddedByteArray[32](data.string) + var hashData = strToHash(data.string) let body = chain.getBlockBody(hashData) + if body == nil: + raise newException(ValueError, "Cannot find hash") result = body.uncles.len rpcsrv.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int: @@ -171,6 +181,8 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = let header = chain.headerFromTag(quantityTag) body = chain.getBlockBody(header.hash) + if body == nil: + raise newException(ValueError, "Cannot find hash") result = body.uncles.len rpcsrv.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr: @@ -180,9 +192,9 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. ## Returns the code from the given address. let - account_db = accountDbFromTag(quantityTag) + accountDb = accountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) - storage = account_db.getCode(addrBytes) + storage = accountDb.getCode(addrBytes) # Easier to return the string manually here rather than expect ByteRange to be marshalled result = byteutils.toHex(storage.toOpenArray).HexDataStr @@ -261,7 +273,9 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = result.gasUsed = header.gasUsed result.timestamp = header.timeStamp result.transactions = blockBody.transactions - result.uncles = blockBody.uncles + result.uncles = @[] + for i in 0 ..< blockBody.uncles.len: + result.uncles[i] = blockBody.uncles[i].hash rpcsrv.rpc("eth_getBlockByHash") do(data: HexDataStr, fullTransactions: bool) -> BlockObject: ## Returns information about a block by hash. @@ -270,8 +284,11 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## 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.getCanonicalHead() - body = chain.getBlockBody(header.hash) + h = data.string.strToHash + header = chain.getBlockHeader(h) + body = chain.getBlockBody(h) + if body == nil: + raise newException(ValueError, "Cannot find hash") populateBlockObject(header, body) rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject: @@ -283,14 +300,52 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = let header = chain.headerFromTag(quantityTag) body = chain.getBlockBody(header.hash) + if body == nil: + raise newException(ValueError, "Cannot find hash") populateBlockObject(header, body) + func populateTransactionObject(transaction: Transaction, txHash: Hash256, txCount: UInt256, txIndex: int, blockHeader: BlockHeader, gas: int64): TransactionObject = + result.hash = txHash + result.nonce = txCount + result.blockHash = new Hash256 + result.blockHash[] = blockHeader.hash + result.blockNumber = new BlockNumber + result.blockNumber[] = blockHeader.blockNumber + result.transactionIndex = new int64 + result.transactionIndex[] = txIndex + # TODO: Fetch or calculate `from` address with signature after signing with the private key + #result.source: EthAddress + result.to = new EthAddress + result.to[] = transaction.to + result.value = transaction.value + result.gasPrice = transaction.gasPrice + result.gas = gas + result.input = transaction.payload + rpcsrv.rpc("eth_getTransactionByHash") do(data: HexDataStr) -> TransactionObject: ## Returns the information about a transaction requested by transaction hash. ## ## data: hash of a transaction. ## Returns requested transaction information. - discard + let + h = data.string.strToHash() + txDetails = chain.getTransactionKey(h) + header = chain.getBlockHeader(txDetails.blockNumber) + blockHash = chain.getBlockHash(txDetails.blockNumber) + body = chain.getBlockBody(blockHash) + if body == nil: + raise newException(ValueError, "Cannot find hash") + let + transaction = body.transactions[txDetails.index] + vmState = newBaseVMState(header, chain) + addressDb = vmState.chaindb.getStateDb(blockHash, true) + # TODO: Get/calculate address for this transaction + address = ZERO_ADDRESS + txCount = addressDb.getNonce(address) + txHash = transaction.rlpHash + # TODO: Fetch account gas + accountGas = 0 + populateTransactionObject(transaction, txHash, txCount, txDetails.index, header, accountGas) rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> TransactionObject: ## Returns information about a transaction by block hash and transaction index position. @@ -298,41 +353,122 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## data: hash of a block. ## quantity: integer of the transaction index position. ## Returns requested transaction information. - discard + let + blockHash = data.string.strToHash() + body = chain.getBlockBody(blockHash) + if body == nil: + raise newException(ValueError, "Cannot find hash") + let + header = chain.getBlockHeader(blockHash) + transaction = body.transactions[quantity] + vmState = newBaseVMState(header, chain) + addressDb = vmState.chaindb.getStateDb(blockHash, true) + # TODO: Get/calculate address for this transaction + address = ZERO_ADDRESS + txCount = addressDb.getNonce(address) + txHash = transaction.rlpHash + # TODO: Fetch account gas + accountGas = 0 + populateTransactionObject(transaction, txHash, txCount, quantity, header, accountGas) + rpcsrv.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. ## quantity: the transaction index position. - discard + let + header = chain.headerFromTag(quantityTag) + blockHash = header.hash + body = chain.getBlockBody(blockHash) + if body == nil: + raise newException(ValueError, "Cannot find hash") + let + transaction = body.transactions[quantity] + vmState = newBaseVMState(header, chain) + addressDb = vmState.chaindb.getStateDb(blockHash, true) + # TODO: Get/calculate address for this transaction + address = ZERO_ADDRESS + txCount = addressDb.getNonce(address) + txHash = transaction.rlpHash + # TODO: Fetch account gas + accountGas = 0 + populateTransactionObject(transaction, txHash, txCount, quantity, header, accountGas) # Currently defined as a variant type so this might need rethinking # See: https://github.com/status-im/nim-json-rpc/issues/29 - #[ + + proc populateReceipt(receipt: Receipt, transaction: Transaction, txIndex: int, blockHeader: BlockHeader): ReceiptObject = + result.transactionHash = transaction.rlpHash + result.transactionIndex = txIndex + result.blockHash = blockHeader.hash + result.blockNumber = blockHeader.blockNumber + # TODO: Get sender + #result.sender: EthAddress + result.to = new EthAddress + result.to[] = transaction.to + # TODO: Get gas used + #result.cumulativeGasUsed: int + #result.gasUsed: int + # TODO: Get contract address if the transaction was a contract creation. + result.contractAddress = nil + # TODO: See Wiki for details. list of log objects, which this transaction generated. + result.logs = @[] + result.logsBloom = blockHeader.bloom + # post-transaction stateroot (pre Byzantium). + result.root = blockHeader.stateRoot + # 1 = success, 0 = failure. + result.status = 1 + rpcsrv.rpc("eth_getTransactionReceipt") do(data: HexDataStr) -> ReceiptObject: ## Returns the receipt of a transaction by transaction hash. ## ## data: hash of a transaction. ## Returns transaction receipt. - discard - ]# + let + h = data.string.strToHash() + txDetails = chain.getTransactionKey(h) + header = chain.getBlockHeader(txDetails.blockNumber) + body = chain.getBlockBody(h) + if body == nil: + raise newException(ValueError, "Cannot find hash") + var idx = 0 + for receipt in chain.getReceipts(header, Receipt): + if idx == txDetails.index: + return populateReceipt(receipt, body.transactions[txDetails.index], txDetails.index, header) + idx.inc - rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int64) -> BlockObject: + rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> BlockObject: ## Returns information about a uncle of a block by hash and uncle index position. ## - ## data: hash a block. + ## data: hash of block. ## quantity: the uncle's index position. ## Returns BlockObject or nil when no block was found. - discard + let + blockHash = data.string.strToHash() + body = chain.getBlockBody(blockHash) + if body == nil: + raise newException(ValueError, "Cannot find hash") + if quantity < 0 or quantity >= body.uncles.len: + raise newException(ValueError, "Uncle index out of range") + let uncle = body.uncles[quantity] + result = populateBlockObject(uncle, body) - rpcsrv.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int64) -> BlockObject: + rpcsrv.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> 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. - discard + let + header = chain.headerFromTag(quantityTag) + body = chain.getBlockBody(header.hash) + if body == nil: + raise newException(ValueError, "Cannot find hash") + if quantity < 0 or quantity >= body.uncles.len: + raise newException(ValueError, "Uncle index out of range") + let uncle = body.uncles[quantity] + result = populateBlockObject(uncle, body) # FilterOptions requires more layout planning. # See: https://github.com/status-im/nim-json-rpc/issues/29 @@ -374,7 +510,6 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns true if the filter was successfully uninstalled, otherwise false. discard - #[ rpcsrv.rpc("eth_getFilterChanges") do(filterId: int) -> seq[LogObject]: ## Polling method for a filter, which returns an list of logs which occurred since last poll. ## @@ -386,6 +521,7 @@ proc setupP2PRPC*(node: EthereumNode, rpcsrv: RpcServer) = ## Returns a list of all logs matching filter with given id. result = @[] + #[ rpcsrv.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]: ## filterOptions: settings for this filter. ## Returns a list of all logs matching a given filter object. diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index ce86d7c2a..050f04e46 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -59,21 +59,21 @@ type 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. - uncles*: seq[BlockHeader] # list of uncle hashes. + 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*: int64 # TODO: Is int? the number of transactions made by the sender prior to this one. + nonce*: UInt256 # the number of transactions made by the sender prior to this one. blockHash*: ref Hash256 # hash of the block where this transaction was in. null when its pending. blockNumber*: ref BlockNumber # block number where this transaction was in. null when its pending. transactionIndex*: ref int64 # integer of the transactions index position in the block. null when its pending. source*: EthAddress # address of the sender. to*: ref EthAddress # address of the receiver. null when its a contract creation transaction. - value*: int64 # value transferred in Wei. + value*: UInt256 # value transferred in Wei. gasPrice*: GasInt # gas price provided by the sender in Wei. gas*: GasInt # gas provided by the sender. - input*: HexDataStr # the data send along with the transaction. + input*: Blob # the data send along with the transaction. LogObject* = object # Returned to user @@ -88,3 +88,20 @@ type topics*: array[4, Hash256] # array of 0 to 4 32 Bytes DATA of indexed log arguments. # (In solidity: The first topic is the hash of the signature of the event. # (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.) + + 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. + 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. + to*: ref EthAddress # address of the receiver. null when its a contract creation transaction. + cumulativeGasUsed*: int # the total amount of gas used when this transaction was executed in the block. + gasUsed*: int # the amount of gas used by this specific transaction alone. + contractAddress*: ref EthAddress # the contract address created, if the transaction was a contract creation, otherwise null. + logs*: seq[LogObject] # TODO: See Wiki for details. list of log objects, which this transaction generated. + logsBloom*: BloomFilter # bloom filter for light clients to quickly retrieve related logs. + root*: Hash256 # post-transaction stateroot (pre Byzantium). + status*: int # 1 = success, 0 = failure. +