diff --git a/nimbus/rpc/common.nim b/nimbus/rpc/common.nim index 8ad295e6d..507a50ed1 100644 --- a/nimbus/rpc/common.nim +++ b/nimbus/rpc/common.nim @@ -9,12 +9,12 @@ import strutils, nimcrypto, eth_common, stint, eth_trie/[memdb, types] import json_rpc/server, ../vm_state, ../logging, ../db/[db_chain, state_db], - ../constants, ../config + ../constants, ../config, hexstrings proc setupCommonRPC*(server: RpcServer) = server.rpc("web3_clientVersion") do() -> string: result = NimbusIdent - server.rpc("web3_sha3") do(data: string) -> string: - var rawdata = nimcrypto.fromHex(data) + server.rpc("web3_sha3") do(data: HexDataStr) -> string: + var rawdata = nimcrypto.fromHex(data.string) result = "0x" & $keccak_256.digest(rawdata) diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index 9961d1265..c02d17627 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -17,6 +17,7 @@ * ref EthAddress * Hash256 * UInt256 + * seq[byte] * openArray[seq] * ref BloomFilter ]# @@ -26,13 +27,12 @@ import eth_common/eth_types, stint, byteutils, nimcrypto 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 + WhisperIdentityStr* = distinct string # 60 bytes + HexStrings = HexQuantityStr | HexDataStr | EthAddressStr | EthHashStr | WhisperIdentityStr -func len*(quantity: HexQuantityStr): int = quantity.string.len -func len*(data: HexDataStr): int = data.string.len -func len*(data: EthAddressStr): int = data.string.len -func len*(data: EthHashStr): int = data.string.len +template len*(value: HexStrings): int = value.string.len # Hex validation @@ -91,11 +91,17 @@ func isValidEthHash*(value: string): bool = # TODO: Allow shorter hashes (pad with zeros) for convenience? result = value.len == 66 and value.isValidHexData +func isValidWhisperIdentity*(value: string): bool = + # 60 bytes for WhisperIdentity plus "0x" + # TODO: Are the HexData constratins applicable to Whisper identities? + result = value.len == 122 and value.isValidHexData + const SInvalidQuantity = "Invalid hex quantity format for Ethereum" SInvalidData = "Invalid hex data format for Ethereum" SInvalidAddress = "Invalid address format for Ethereum" SInvalidHash = "Invalid hash format for Ethereum" + SInvalidWhisperIdentity = "Invalid format for whisper identity" proc validateHexQuantity*(value: string) {.inline.} = if unlikely(not value.isValidHexQuantity): @@ -113,6 +119,10 @@ proc validateHashStr*(value: string) {.inline.} = if unlikely(not value.isValidEthHash): raise newException(ValueError, SInvalidHash & ": " & value) +proc validateWhisperIdentity*(value: string) {.inline.} = + if unlikely(not value.isValidWhisperIdentity): + raise newException(ValueError, SInvalidWhisperIdentity & ": " & value) + # Initialisation proc hexQuantityStr*(value: string): HexQuantityStr {.inline.} = @@ -131,21 +141,16 @@ proc ethHashStr*(value: string): EthHashStr {.inline.} = value.validateHashStr result = value.EthHashStr +proc whisperIdentity*(value: string): WhisperIdentityStr {.inline.} = + value.validateWhisperIdentity + result = value.WhisperIdentityStr + # Converters for use in RPC import json from json_rpc/rpcserver import expect -proc `%`*(value: HexQuantityStr): JsonNode = - result = %(value.string) - -proc `%`*(value: HexDataStr): JsonNode = - result = %(value.string) - -proc `%`*(value: EthAddressStr): JsonNode = - result = %(value.string) - -proc `%`*(value: EthHashStr): JsonNode = +proc `%`*(value: HexStrings): JsonNode = result = %(value.string) # Overloads to support expected representation of hex data @@ -162,8 +167,8 @@ proc `%`*(value: Hash256): JsonNode = proc `%`*(value: UInt256): JsonNode = result = %("0x" & value.toString) -proc `%`*(value: openArray[seq]): JsonNode = - result = %("0x" & value.toHex) +proc `%`*(value: WhisperIdentity): JsonNode = + result = %("0x" & byteutils.toHex(value)) proc `%`*(value: ref BloomFilter): JsonNode = result = %("0x" & toHex[256](value[])) @@ -171,7 +176,6 @@ proc `%`*(value: ref BloomFilter): JsonNode = # Marshalling from JSON to Nim types that includes format checking proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = - # Note that '0x' is stripped after validation n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidHexQuantity: @@ -179,7 +183,6 @@ proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) = result = hexStr.hexQuantityStr proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) = - # Note that '0x' is stripped after validation n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidHexData: @@ -187,7 +190,6 @@ proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) = result = hexStr.hexDataStr proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = - # Note that '0x' is stripped after validation n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidEthAddress: @@ -195,10 +197,16 @@ proc fromJson*(n: JsonNode, argName: string, result: var EthAddressStr) = result = hexStr.EthAddressStr proc fromJson*(n: JsonNode, argName: string, result: var EthHashStr) = - # Note that '0x' is stripped after validation n.kind.expect(JString, argName) let hexStr = n.getStr() if not hexStr.isValidEthHash: raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as an Ethereum hash \"" & hexStr & "\"") result = hexStr.EthHashStr +proc fromJson*(n: JsonNode, argName: string, result: var WhisperIdentityStr) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidWhisperIdentity: + raise newException(ValueError, "Parameter \"" & argName & "\" is not valid as a Whisper identity \"" & hexStr & "\"") + result = hexStr.WhisperIdentityStr + diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index d1bb97a3d..afe64f90d 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -8,7 +8,7 @@ # those terms. import nimcrypto, json_rpc/rpcserver, eth_p2p, hexstrings, strutils, stint, - ../config, ../vm_state, ../constants, eth_trie/[memdb, types], + ../config, ../vm_state, ../constants, eth_trie/[memdb, types], eth_keys, ../db/[db_chain, state_db, storage_types], eth_common, rpc_types, byteutils, ranges/typedranges, times, ../utils/header, rlp @@ -33,6 +33,52 @@ func toHash(value: array[32, byte]): Hash256 {.inline.} = func strToHash(value: string): Hash256 {.inline.} = result = hexToPaddedByteArray[32](value).toHash +func hash(transaction: Transaction): Hash256 = + # Hash transaction without signature + type + TransHashObj = object + accountNonce: uint64 + gasPrice: GasInt + gasLimit: GasInt + to: EthAddress + value: UInt256 + payload: Blob + return TransHashObj( + accountNonce: transaction.accountNonce, + gasPrice: transaction.gasPrice, + gasLimit: transaction.gasLimit, + to: transaction.to, + value: transaction.value, + payload: transaction.payload + ).rlpHash + +proc toSignature(transaction: Transaction): Signature = + var bytes: array[65, byte] + bytes[0..31] = cast[array[32, byte]](transaction.R) + bytes[32..63] = cast[array[32, byte]](transaction.S) + #[ + TODO: In the yellow paper: + It is assumed that v is the ‘recovery id’, a 1 byte value + specifying the sign and finiteness of the curve point; this + value is in the range of [27,30]. + Does this need to be checked that it is [0, 1] and inc by 27? + ]# + # TODO: Ugly casting below, is there a better way/helper func? + bytes[64] = (cast[uint64](transaction.V.data.lo) and 0xff'u64).uint8 + initSignature(bytes) + +proc getSender(transaction: Transaction): EthAddress = + ## Find the address the transaction was sent from. + let + txHash = transaction.hash # hash without signature + sig = transaction.toSignature() + pubKey = recoverKeyFromSignature(sig, txHash) + result = pubKey.toCanonicalAddress() + +template balance(addressDb: AccountStateDb, address: EthAddress): GasInt = + # TODO: Account balance u256 but GasInt is int64? + cast[GasInt](addressDb.get_balance(address).data.lo) + func headerFromTag(chain:BaseChainDB, blockTag: string): BlockHeader = let tag = blockTag.toLowerAscii case tag @@ -102,14 +148,14 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns integer of the current block number the client is on. result = chain.getCanonicalHead().blockNumber - rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> int: + rpcsrv.rpc("eth_getBalance") do(data: EthAddressStr, quantityTag: string) -> GasInt: ## Returns the balance of the account of given address. ## ## data: address to check for balance. ## 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 - accountDb = accountDbFromTag(quantityTag) + accountDb = getAccountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) balance = accountDb.get_balance(addrBytes) @@ -123,7 +169,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, 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 - accountDb = accountDbFromTag(quantityTag) + accountDb = getAccountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) storage = accountDb.getStorage(addrBytes, quantity.u256) if storage[1]: @@ -137,7 +183,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns integer of the number of transactions send from this address. let addrBytes = data.string.strToAddress() - accountDb = accountDbFromTag(quantityTag) + accountDb = getAccountDbFromTag(quantityTag) result = accountDb.getNonce(addrBytes) rpcsrv.rpc("eth_getBlockTransactionCountByHash") do(data: HexDataStr) -> int: @@ -179,12 +225,17 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, 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 - accountDb = accountDbFromTag(quantityTag) + accountDb = getAccountDbFromTag(quantityTag) addrBytes = strToAddress(data.string) storage = accountDb.getCode(addrBytes) # Easier to return the string manually here rather than expect ByteRange to be marshalled result = byteutils.toHex(storage.toOpenArray).HexDataStr + template sign(privateKey: PrivateKey, message: string): string = + # TODO: Is message length encoded as bytes or characters? + let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message + $signMessage(privateKey, msgData) + rpcsrv.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. @@ -194,7 +245,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## data: address. ## message: message to sign. ## Returns signature. - discard + let accountDb = getAccountDb(true) + var privateKey: PrivateKey # TODO: Get from key store + result = ("0x" & sign(privateKey, message.string)).HexDataStr rpcsrv.rpc("eth_sendTransaction") do(obj: EthSend) -> HexDataStr: ## Creates new message call transaction or a contract creation, if the data field contains code. @@ -220,7 +273,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns the return value of executed contract. discard - rpcsrv.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> HexDataStr: # TODO: Int or U/Int256? + rpcsrv.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. @@ -231,10 +284,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = discard func populateBlockObject(header: BlockHeader, blockBody: BlockBody): BlockObject = - result.number = new BlockNumber - result.number[] = header.blockNumber - result.hash = new Hash256 - result.hash[] = header.hash + result.number = some(header.blockNumber) + result.hash = some(header.hash) result.parentHash = header.parentHash result.nonce = header.nonce.toUint @@ -247,7 +298,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = startIdx += 32 result.sha3Uncles = keccak256.digest(rawData) - result.logsBloom = nil # TODO: Create bloom filter for logs + result.logsBloom = some(header.bloom) result.transactionsRoot = header.txRoot result.stateRoot = header.stateRoot result.receiptsRoot = header.receiptRoot @@ -264,7 +315,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: HexDataStr, fullTransactions: bool) -> BlockObject: + rpcsrv.rpc("eth_getBlockByHash") do(data: HexDataStr, fullTransactions: bool) -> Option[BlockObject]: ## Returns information about a block by hash. ## ## data: Hash of a block. @@ -273,9 +324,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = let h = data.string.strToHash header = chain.getBlockHeader(h) - populateBlockObject(header, getBlockBody(h)) + result = some(populateBlockObject(header, getBlockBody(h))) - rpcsrv.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject: + rpcsrv.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. @@ -283,26 +334,29 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## Returns BlockObject or nil when no block was found. let header = chain.headerFromTag(quantityTag) - populateBlockObject(header, getBlockBody(header.hash)) + result = some(populateBlockObject(header, getBlockBody(header.hash))) + + proc populateTransactionObject(transaction: Transaction, txIndex: int64, blockHeader: BlockHeader, blockHash: Hash256): TransactionObject = + let + vmState = newBaseVMState(blockHeader, chain) + accountDb = vmState.chaindb.getStateDb(blockHash, true) + address = transaction.getSender() + txCount = accountDb.getNonce(address) + txHash = transaction.rlpHash + accountGas = accountDb.balance(address) - 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.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 = gas + result.gas = accountGas result.input = transaction.payload - + rpcsrv.rpc("eth_getTransactionByHash") do(data: HexDataStr) -> TransactionObject: ## Returns the information about a transaction requested by transaction hash. ## @@ -314,15 +368,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = header = chain.getBlockHeader(txDetails.blockNumber) blockHash = chain.getBlockHash(txDetails.blockNumber) transaction = getBlockBody(blockHash).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) + populateTransactionObject(transaction, txDetails.index, header, blockHash) rpcsrv.rpc("eth_getTransactionByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> TransactionObject: ## Returns information about a transaction by block hash and transaction index position. @@ -334,16 +380,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = blockHash = data.string.strToHash() header = chain.getBlockHeader(blockHash) transaction = getBlockBody(blockHash).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) - + populateTransactionObject(transaction, quantity, header, blockHash) rpcsrv.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> TransactionObject: ## Returns information about a transaction by block number and transaction index position. @@ -354,38 +391,24 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = header = chain.headerFromTag(quantityTag) blockHash = header.hash transaction = getBlockBody(blockHash).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) + populateTransactionObject(transaction, quantity, header, blockHash) - # 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 = + proc populateReceipt(receipt: Receipt, cumulativeGas: GasInt, 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 + result.sender = transaction.getSender() + result.to = some(transaction.to) + result.cumulativeGasUsed = cumulativeGas + result.gasUsed = receipt.gasUsed # 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.contractAddress = none(EthAddress) + result.logs = receipt.logs result.logsBloom = blockHeader.bloom # post-transaction stateroot (pre Byzantium). result.root = blockHeader.stateRoot + # TODO: Respond to success/failure # 1 = success, 0 = failure. result.status = 1 @@ -399,13 +422,16 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = txDetails = chain.getTransactionKey(h) header = chain.getBlockHeader(txDetails.blockNumber) body = getBlockBody(header.hash) - var idx = 0 + var + idx = 0 + cumulativeGas: GasInt for receipt in chain.getReceipts(header, Receipt): + cumulativeGas += receipt.gasUsed if idx == txDetails.index: - return populateReceipt(receipt, body.transactions[txDetails.index], txDetails.index, header) + return populateReceipt(receipt, cumulativeGas, body.transactions[txDetails.index], txDetails.index, header) idx.inc - rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> BlockObject: + rpcsrv.rpc("eth_getUncleByBlockHashAndIndex") do(data: HexDataStr, quantity: int) -> Option[BlockObject]: ## Returns information about a uncle of a block by hash and uncle index position. ## ## data: hash of block. @@ -417,9 +443,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = 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) + result = some(populateBlockObject(uncle, body)) - rpcsrv.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> BlockObject: + rpcsrv.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. @@ -431,11 +457,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = 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) + result = some(populateBlockObject(uncle, body)) - # FilterOptions requires more layout planning. - # See: https://github.com/status-im/nim-json-rpc/issues/29 - #[ rpcsrv.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. @@ -449,7 +472,6 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) = ## filterOptions: settings for this filter. ## Returns integer filter id. discard - ]# rpcsrv.rpc("eth_newBlockFilter") do() -> int: ## Creates a filter in the node, to notify when a new block arrives. @@ -473,31 +495,29 @@ 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[LogObject]: + rpcsrv.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[LogObject]: + rpcsrv.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[LogObject]: + rpcsrv.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() -> seq[HexDataStr]: + rpcsrv.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. ## DATA, 32 Bytes - the seed hash used for the DAG. ## DATA, 32 Bytes - the boundary condition ("target"), 2^256 / difficulty. - result = @[] + discard rpcsrv.rpc("eth_submitWork") do(nonce: int64, powHash: HexDataStr, mixDigest: HexDataStr) -> bool: ## Used for submitting a proof-of-work solution. diff --git a/nimbus/rpc/rpc_types.nim b/nimbus/rpc/rpc_types.nim index 050f04e46..4141324a2 100644 --- a/nimbus/rpc/rpc_types.nim +++ b/nimbus/rpc/rpc_types.nim @@ -1,9 +1,9 @@ -import eth_common, hexstrings +import eth_common, hexstrings, options #[ Notes: * Some of the types suppose 'null' when there is no appropriate value. - To allow for this, currently these values are refs so the JSON transform can convert to `JNull`. + To allow for this, you can use Option[T] or use refs so the JSON transform can convert to `JNull`. * Parameter objects from users must have their data verified so will use EthAddressStr instead of EthAddres, for example * Objects returned to the user can use native Nimbus types, where hexstrings provides converters to hex strings. This is because returned arrays in JSON is @@ -39,14 +39,14 @@ type ## A block object, or null when no block was found ## Note that this includes slightly different information from eth_common.BlockHeader - BlockObject* = ref object + BlockObject* = object # Returned to user - number*: ref BlockNumber # the block number. null when its pending block. - hash*: ref Hash256 # hash of the block. null when its pending block. + number*: Option[BlockNumber] # 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. sha3Uncles*: Hash256 # SHA3 of the uncles data in the block. - logsBloom*: ref BloomFilter # the bloom filter for the logs of the block. null when its pending 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. @@ -63,45 +63,82 @@ type TransactionObject* = object # A transaction object, or null when no transaction was found: # Returned to user - hash*: Hash256 # hash of the transaction. - 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*: UInt256 # value transferred in Wei. - gasPrice*: GasInt # gas price provided by the sender in Wei. - gas*: GasInt # gas provided by the sender. - input*: Blob # the data send along with the transaction. + hash*: Hash256 # hash of the transaction. + nonce*: UInt256 # 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. + input*: Blob # the data send along with the transaction. - LogObject* = object + FilterLog* = object # Returned to user - removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log. - logIndex*: ref int # integer of the log index position in the block. null when its pending log. - transactionIndex*: ref int # integer of the transactions index position log was created from. null when its pending log. - transactionHash*: ref Hash256 # hash of the transactions this log was created from. null when its pending log. - blockHash*: ref Hash256 # hash of the block where this log was in. null when its pending. null when its pending log. - blockNumber*: ref BlockNumber # the block number where this log was in. null when its pending. null when its pending log. - address*: EthAddress # address from which this log originated. - data*: seq[Hash256] # contains one or more 32 Bytes non-indexed arguments of the log. - 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.) + removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log. + logIndex*: Option[int] # integer of the log index position in the block. null when its pending log. + transactionIndex*: Option[int] # integer of the transactions index position log was created from. null when its pending log. + transactionHash*: Option[Hash256] # hash of the transactions this log was created from. null when its pending log. + blockHash*: Option[Hash256] # hash of the block where this log was in. null when its pending. null when its pending log. + blockNumber*: Option[BlockNumber] # the block number where this log was in. null when its pending. null when its pending log. + address*: EthAddress # address from which this log originated. + data*: seq[Hash256] # contains one or more 32 Bytes non-indexed arguments of the log. + 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. + 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*: 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. + 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. + root*: Hash256 # post-transaction stateroot (pre Byzantium). + status*: int # 1 = success, 0 = failure. + FilterDataKind* = enum fkItem, fkList + FilterData* = object + # Difficult to process variant objects in input data, as kind is immutable. + # TODO: This might need more work to handle "or" options + kind*: FilterDataKind + items*: seq[FilterData] + item*: UInt256 + + FilterOptions* = object + # Parameter from user + fromBlock*: string # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + toBlock*: string # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions. + address*: EthAddress # (optional) contract address or a list of addresses from which logs should originate. + topics*: seq[FilterData] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options. + + WhisperPost* = object + # Parameter from user + source*: WhisperIdentityStr # (optional) the identity of the sender. + to*: WhisperIdentityStr # (optional) the identity of the receiver. When present whisper will encrypt the message so that only the receiver can decrypt it. + topics*: seq[HexDataStr] # list of DATA topics, for the receiver to identify messages. + payload*: HexDataStr # the payload of the message. + priority*: int # integer of the priority in a rang from. + ttl*: int # integer of the time to live in seconds. + + WhisperIdentity = array[60, byte] + + WhisperMessage* = object + # Returned to user + hash*: Hash256 # the hash of the message. + source*: WhisperIdentity # the sender of the message, if a sender was specified. + to*: WhisperIdentity # the receiver of the message, if a receiver was specified. + expiry*: int # integer of the time in seconds when this message should expire. + ttl*: int # integer of the time the message should float in the system in seconds. + sent*: int # integer of the unix timestamp when the message was sent. + topics*: seq[UInt256] # list of DATA topics the message contained. + payload*: Blob # the payload of the message. + workProved*: int # integer of the work this message required before it was send. diff --git a/nimbus/rpc/whisper.nim b/nimbus/rpc/whisper.nim new file mode 100644 index 000000000..dcb5abc2e --- /dev/null +++ b/nimbus/rpc/whisper.nim @@ -0,0 +1,74 @@ +import json_rpc/rpcserver, rpc_types, stint, hexstrings, eth_common + +proc setupWhisperRPC*(rpcsrv: RpcServer) = + rpcsrv.rpc("shh_version") do() -> string: + ## Returns string of the current whisper protocol version. + discard + + rpcsrv.rpc("shh_post") do(message: WhisperPost) -> bool: + ## Sends a whisper message. + ## + ## message: Whisper message to post. + ## Returns true if the message was send, otherwise false. + discard + + rpcsrv.rpc("shh_newIdentity") do() -> WhisperIdentity: + ## Creates new whisper identity in the client. + ## + ## Returns the address of the new identiy. + discard + + rpcsrv.rpc("shh_hasIdentity") do(identity: WhisperIdentityStr) -> bool: + ## Checks if the client holds the private keys for a given identity. + ## + ## identity: the identity address to check. + ## Returns true if the client holds the privatekey for that identity, otherwise false. + discard + + rpcsrv.rpc("shh_newGroup") do() -> WhisperIdentity: + ## (?) - This has no description information in the RPC wiki. + ## + ## Returns the address of the new group. (?) + discard + + rpcsrv.rpc("shh_addToGroup") do(identity: WhisperIdentityStr) -> bool: + ## (?) - This has no description information in the RPC wiki. + ## + ## identity: the identity address to add to a group (?). + ## Returns true if the identity was successfully added to the group, otherwise false (?). + discard + + rpcsrv.rpc("shh_newFilter") do(filterOptions: FilterOptions, to: WhisperIdentityStr, topics: seq[HexDataStr]) -> int: + ## Creates filter to notify, when client receives whisper message matching the filter options. + ## + ## filterOptions: The filter options: + ## to: DATA, 60 Bytes - (optional) identity of the receiver. When present it will try to decrypt any incoming message if the client holds the private key to this identity. + ## topics: Array of DATA - list of DATA topics which the incoming message's topics should match. You can use the following combinations: + ## [A, B] = A && B + ## [A, [B, C]] = A && (B || C) + ## [null, A, B] = ANYTHING && A && B null works as a wildcard + ## Returns the newly created filter. + discard + + rpcsrv.rpc("shh_uninstallFilter") do(id: 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 shh_getFilterChanges for a period of time. + ## + ## id: the filter id. + ## Returns true if the filter was successfully uninstalled, otherwise false. + discard + + rpcsrv.rpc("shh_getFilterChanges") do(id: int) -> seq[WhisperMessage]: + ## Polling method for whisper filters. Returns new messages since the last call of this method. + ## Note: calling the shh_getMessages method, will reset the buffer for this method, so that you won't receive duplicate messages. + ## + ## id: the filter id. + discard + + rpcsrv.rpc("shh_getMessages") do(id: int) -> seq[WhisperMessage]: + ## Get all messages matching a filter. Unlike shh_getFilterChanges this returns all messages. + ## + ## id: the filter id. + ## Returns a list of messages received since last poll. + discard