diff --git a/nimbus/rpc/hexstrings.nim b/nimbus/rpc/hexstrings.nim index cec7fda2e..67fb1c939 100644 --- a/nimbus/rpc/hexstrings.nim +++ b/nimbus/rpc/hexstrings.nim @@ -93,6 +93,9 @@ func isValidHexData*(value: string, header = true): bool = return false return true +template isValidHexData(value: string, hexLen: int, header = true): bool = + value.len == hexLen and value.isValidHexData(header) + func isValidEthAddress*(value: string): bool = # 20 bytes for EthAddress plus "0x" # Addresses are allowed to be shorter than 20 bytes for convenience @@ -102,27 +105,31 @@ func isValidEthHash*(value: string): bool = # 32 bytes for EthAddress plus "0x" # Currently hashes are required to be exact lengths # TODO: Allow shorter hashes (pad with zeros) for convenience? - result = value.len == 66 and value.isValidHexData + result = value.isValidHexData(66) func isValidIdentifier*(value: string): bool = # 32 bytes for Whisper ID, no 0x prefix - result = value.len == 64 and value.isvalidHexData(header = false) + result = value.isValidHexData(64, false) func isValidPublicKey*(value: string): bool = # 65 bytes for Public Key plus 1 byte for 0x prefix - result = value.len == 132 and value.isValidHexData + result = value.isValidHexData(132) func isValidPrivateKey*(value: string): bool = # 32 bytes for Private Key plus 1 byte for 0x prefix - result = value.len == 66 and value.isValidHexData + result = value.isValidHexData(66) func isValidSymKey*(value: string): bool = # 32 bytes for Private Key plus 1 byte for 0x prefix - result = value.len == 66 and value.isValidHexData + result = value.isValidHexData(66) + +func isValidHash256*(value: string): bool = + # 32 bytes for Hash256 plus 1 byte for 0x prefix + result = value.isValidHexData(66) func isValidTopic*(value: string): bool = # 4 bytes for Topic plus 1 byte for 0x prefix - result = value.len == 10 and value.isValidHexData + result = value.isValidHexData(10) const SInvalidQuantity = "Invalid hex quantity format for Ethereum" @@ -283,3 +290,34 @@ proc fromJson*(n: JsonNode, argName: string, result: var TopicStr) = if not hexStr.isValidTopic: raise newException(ValueError, invalidMsg(argName) & " as a topic \"" & hexStr & "\"") result = hexStr.TopicStr + +# Following procs currently required only for testing, the `createRpcSigs` macro +# requires it as it will convert the JSON results back to the original Nim +# types, but it needs the `fromJson` calls for those specific Nim types to do so +proc fromJson*(n: JsonNode, argName: string, result: var whisper_protocol.Topic) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidTopic: + raise newException(ValueError, invalidMsg(argName) & " as a topic \"" & hexStr & "\"") + hexToByteArray(hexStr.string[2 .. ^1], result) + +proc fromJson*(n: JsonNode, argName: string, result: var Bytes) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidHexData: + raise newException(ValueError, invalidMsg(argName) & " as a hex data \"" & hexStr & "\"") + result = hexToSeqByte(hexStr.string) + +proc fromJson*(n: JsonNode, argName: string, result: var Hash256) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidHash256: + raise newException(ValueError, invalidMsg(argName) & " as a Hash256 \"" & hexStr & "\"") + hexToByteArray(hexStr.string, result.data) + +proc fromJson*(n: JsonNode, argName: string, result: var PublicKey) = + n.kind.expect(JString, argName) + let hexStr = n.getStr() + if not hexStr.isValidPublicKey: + raise newException(ValueError, invalidMsg(argName) & " as a public key \"" & hexStr & "\"") + result = initPublicKey(hexStr.string[4 .. ^1]) diff --git a/tests/test_rpc_whisper.nim b/tests/test_rpc_whisper.nim index 8c1d06043..7bcb1c097 100644 --- a/tests/test_rpc_whisper.nim +++ b/tests/test_rpc_whisper.nim @@ -1,5 +1,5 @@ import - unittest, strformat, options, json_rpc/[rpcserver, rpcclient], + unittest, strformat, options, byteutils, json_rpc/[rpcserver, rpcclient], eth/common as eth_common, eth/p2p as eth_p2p, eth/[rlp, keys], eth/p2p/rlpx_protocols/whisper_protocol, ../nimbus/rpc/[common, hexstrings, rpc_types, whisper], ../nimbus/config @@ -113,41 +113,100 @@ proc doTests = waitFor(client.shh_hasSymKey(keyID3)) == false waitFor(client.shh_deleteSymKey(keyID3)) == false - test "shh symKey post and filter": - var options: WhisperFilterOptions - options.symKeyID = some(waitFor client.shh_newSymKey()) - options.topics = some(@["0x12345678".TopicStr]) - let filterID = waitFor client.shh_newMessageFilter(options) + # Some defaults for the filter & post tests + let + ttl = 30'u64 + topic = "0x12345678" + payload = "0x45879632" + # A very low target and long time so we are sure the test never fails + # because of this + powTarget = 0.001 + powTime = 1.0 - var message: WhisperPostMessage - message.symKeyID = options.symKeyID - message.ttl = 30 - message.topic = some("0x12345678".TopicStr) - message.payload = "0x45879632".HexDataStr - message.powTime = 1.0 - message.powTarget = 0.001 + test "shh symKey post and filter loop": + let + symKeyID = waitFor client.shh_newSymKey() + options = WhisperFilterOptions(symKeyID: some(symKeyID), + topics: some(@[topic.TopicStr])) + filterID = waitFor client.shh_newMessageFilter(options) + message = WhisperPostMessage(symKeyID: some(symKeyID), + ttl: ttl, + topic: some(topic.TopicStr), + payload: payload.HexDataStr, + powTime: powTime, + powTarget: powTarget) check: + waitFor(client.shh_setMinPoW(powTarget)) == true waitFor(client.shh_post(message)) == true - # TODO: this does not work due to overloads? - # var messages = waitFor client.shh_getFilterMessages(filterID) - test "shh asymKey post and filter": - var options: WhisperFilterOptions - let keyID = waitFor client.shh_newKeyPair() - options.privateKeyID = some(keyID) - let filterID = waitFor client.shh_newMessageFilter(options) - - var message: WhisperPostMessage - message.pubKey = some(waitFor(client.shh_getPublicKey(keyID))) - message.ttl = 30 - message.topic = some("0x12345678".TopicStr) - message.payload = "0x45879632".HexDataStr - message.powTime = 1.0 - message.powTarget = 0.001 + let messages = waitFor client.shh_getFilterMessages(filterID) check: + messages.len == 1 + messages[0].sig.isNone() + messages[0].recipientPublicKey.isNone() + messages[0].ttl == ttl + ("0x" & messages[0].topic.toHex) == topic + ("0x" & messages[0].payload.toHex) == payload + messages[0].padding.len > 0 + messages[0].pow >= powTarget + + test "shh asymKey post and filter loop": + let + privateKeyID = waitFor client.shh_newKeyPair() + options = WhisperFilterOptions(privateKeyID: some(privateKeyID)) + filterID = waitFor client.shh_newMessageFilter(options) + pubKey = waitFor client.shh_getPublicKey(privateKeyID) + message = WhisperPostMessage(pubKey: some(pubKey), + ttl: ttl, + topic: some(topic.TopicStr), + payload: payload.HexDataStr, + powTime: powTime, + powTarget: powTarget) + check: + waitFor(client.shh_setMinPoW(powTarget)) == true waitFor(client.shh_post(message)) == true - # TODO: this does not work due to overloads? - # var messages = waitFor client.shh_getFilterMessages(filterID) + + let messages = waitFor client.shh_getFilterMessages(filterID) + check: + messages.len == 1 + messages[0].sig.isNone() + ("0x04" & $messages[0].recipientPublicKey.get()) == pubKey.string + messages[0].ttl == ttl + ("0x" & messages[0].topic.toHex) == topic + ("0x" & messages[0].payload.toHex) == payload + messages[0].padding.len > 0 + messages[0].pow >= powTarget + + test "shh signature in post and filter loop": + let + symKeyID = waitFor client.shh_newSymKey() + privateKeyID = waitFor client.shh_newKeyPair() + pubKey = waitFor client.shh_getPublicKey(privateKeyID) + options = WhisperFilterOptions(symKeyID: some(symKeyID), + topics: some(@[topic.TopicStr]), + sig: some(pubKey)) + filterID = waitFor client.shh_newMessageFilter(options) + message = WhisperPostMessage(symKeyID: some(symKeyID), + sig: some(privateKeyID), + ttl: ttl, + topic: some(topic.TopicStr), + payload: payload.HexDataStr, + powTime: powTime, + powTarget: powTarget) + check: + waitFor(client.shh_setMinPoW(powTarget)) == true + waitFor(client.shh_post(message)) == true + + let messages = waitFor client.shh_getFilterMessages(filterID) + check: + messages.len == 1 + ("0x04" & $messages[0].sig.get()) == pubKey.string + messages[0].recipientPublicKey.isNone() + messages[0].ttl == ttl + ("0x" & messages[0].topic.toHex) == topic + ("0x" & messages[0].payload.toHex) == payload + messages[0].padding.len > 0 + messages[0].pow >= powTarget rpcServer.stop() rpcServer.close()