diff --git a/Makefile b/Makefile index 4f745b11c..24dedd030 100644 --- a/Makefile +++ b/Makefile @@ -82,6 +82,11 @@ update: | update-common # a phony target, because teaching `make` how to do conditional recompilation of Nim projects is too complicated +# Whisper tests +testwhisper: | build deps + echo -e $(BUILD_MSG) "build/$@" && \ + $(ENV_SCRIPT) nim testwhisper $(NIM_PARAMS) waku.nims + # Waku v1 targets wakunode1: | build deps echo -e $(BUILD_MSG) "build/$@" && \ diff --git a/tests/all_tests_whisper.nim b/tests/all_tests_whisper.nim new file mode 100644 index 000000000..2d5b5245c --- /dev/null +++ b/tests/all_tests_whisper.nim @@ -0,0 +1,5 @@ +import + # Whisper tests + ./whisper/test_shh, + ./whisper/test_shh_config, + ./whisper/test_shh_connect diff --git a/tests/whisper/test_shh.nim b/tests/whisper/test_shh.nim new file mode 100644 index 000000000..0b70d3dd8 --- /dev/null +++ b/tests/whisper/test_shh.nim @@ -0,0 +1,382 @@ +# +# Ethereum P2P +# (c) Copyright 2018-2021 +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +{.used.} + +import + std/[sequtils, options, unittest, tables], + nimcrypto/hash, + eth/[keys, rlp], + ../../waku/whisper/whisper_types as whisper + +let rng = newRng() + +suite "Whisper payload": + test "should roundtrip without keys": + let payload = Payload(payload: @[byte 0, 1, 2]) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().src.isNone() + decoded.get().padding.get().len == 251 # 256 -1 -1 -3 + + test "should roundtrip with symmetric encryption": + var symKey: SymKey + let payload = Payload(symKey: some(symKey), payload: @[byte 0, 1, 2]) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get(), symKey = some(symKey)) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().src.isNone() + decoded.get().padding.get().len == 251 # 256 -1 -1 -3 + + test "should roundtrip with signature": + let privKey = PrivateKey.random(rng[]) + + let payload = Payload(src: some(privKey), payload: @[byte 0, 1, 2]) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + privKey.toPublicKey() == decoded.get().src.get() + decoded.get().padding.get().len == 186 # 256 -1 -1 -3 -65 + + test "should roundtrip with asymmetric encryption": + let privKey = PrivateKey.random(rng[]) + + let payload = Payload(dst: some(privKey.toPublicKey()), + payload: @[byte 0, 1, 2]) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get(), dst = some(privKey)) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().src.isNone() + decoded.get().padding.get().len == 251 # 256 -1 -1 -3 + + test "should return specified bloom": + # Geth test: https://github.com/ethersphere/go-ethereum/blob/d3441ebb563439bac0837d70591f92e2c6080303/whisper/whisperv6/whisper_test.go#L834 + let top0 = [byte 0, 0, 255, 6] + var x: Bloom + x[0] = byte 1 + x[32] = byte 1 + x[^1] = byte 128 + check @(top0.topicBloom) == @x + +suite "Whisper payload padding": + test "should do max padding": + let payload = Payload(payload: repeat(byte 1, 254)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().padding.isSome() + decoded.get().padding.get().len == 256 # as dataLen == 256 + + test "should do max padding with signature": + let privKey = PrivateKey.random(rng[]) + + let payload = Payload(src: some(privKey), payload: repeat(byte 1, 189)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + privKey.toPublicKey() == decoded.get().src.get() + decoded.get().padding.isSome() + decoded.get().padding.get().len == 256 # as dataLen == 256 + + test "should do min padding": + let payload = Payload(payload: repeat(byte 1, 253)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().padding.isSome() + decoded.get().padding.get().len == 1 # as dataLen == 255 + + test "should do min padding with signature": + let privKey = PrivateKey.random(rng[]) + + let payload = Payload(src: some(privKey), payload: repeat(byte 1, 188)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + privKey.toPublicKey() == decoded.get().src.get() + decoded.get().padding.isSome() + decoded.get().padding.get().len == 1 # as dataLen == 255 + + test "should roundtrip custom padding": + let payload = Payload(payload: repeat(byte 1, 10), + padding: some(repeat(byte 2, 100))) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().padding.isSome() + payload.padding.get() == decoded.get().padding.get() + + test "should roundtrip custom 0 padding": + let padding: seq[byte] = @[] + let payload = Payload(payload: repeat(byte 1, 10), + padding: some(padding)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + decoded.get().padding.isNone() + + test "should roundtrip custom padding with signature": + let privKey = PrivateKey.random(rng[]) + let payload = Payload(src: some(privKey), payload: repeat(byte 1, 10), + padding: some(repeat(byte 2, 100))) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + privKey.toPublicKey() == decoded.get().src.get() + decoded.get().padding.isSome() + payload.padding.get() == decoded.get().padding.get() + + test "should roundtrip custom 0 padding with signature": + let padding: seq[byte] = @[] + let privKey = PrivateKey.random(rng[]) + let payload = Payload(src: some(privKey), payload: repeat(byte 1, 10), + padding: some(padding)) + let encoded = whisper.encode(rng[], payload) + + let decoded = whisper.decode(encoded.get()) + check: + decoded.isSome() + payload.payload == decoded.get().payload + privKey.toPublicKey() == decoded.get().src.get() + decoded.get().padding.isNone() + +# example from https://github.com/paritytech/parity-ethereum/blob/93e1040d07e385d1219d00af71c46c720b0a1acf/whisper/src/message.rs#L439 +let + env0 = Envelope( + expiry:100000, ttl: 30, topic: [byte 0, 0, 0, 0], + data: repeat(byte 9, 256), nonce: 1010101) + env1 = Envelope( + expiry:100000, ttl: 30, topic: [byte 0, 0, 0, 0], + data: repeat(byte 9, 256), nonce: 1010102) + env2 = Envelope( + expiry:100000, ttl: 30, topic: [byte 0, 0, 0, 0], + data: repeat(byte 9, 256), nonce: 1010103) + +suite "Whisper envelope": + + proc hashAndPow(env: Envelope): (string, float64) = + # This is the current implementation of go-ethereum + let size = env.toShortRlp().len().uint32 + # This is our current implementation in `whisper_protocol.nim` + # let size = env.len().uint32 + # This is the EIP-627 specification + # let size = env.toRlp().len().uint32 + let hash = env.calcPowHash() + ($hash, calcPow(size, env.ttl, hash)) + + test "PoW calculation leading zeroes tests": + # Test values from Parity, in message.rs + let testHashes = [ + # 256 leading zeroes + "0x0000000000000000000000000000000000000000000000000000000000000000", + # 255 leading zeroes + "0x0000000000000000000000000000000000000000000000000000000000000001", + # no leading zeroes + "0xff00000000000000000000000000000000000000000000000000000000000000" + ] + check: + calcPow(1, 1, Hash.fromHex(testHashes[0])) == + 115792089237316200000000000000000000000000000000000000000000000000000000000000.0 + calcPow(1, 1, Hash.fromHex(testHashes[1])) == + 57896044618658100000000000000000000000000000000000000000000000000000000000000.0 + calcPow(1, 1, Hash.fromHex(testHashes[2])) == 1.0 + + # Test values from go-ethereum whisperv6 in envelope_test + var env = Envelope(ttl: 1, data: @[byte 0xde, 0xad, 0xbe, 0xef]) + # PoW calculation with no leading zeroes + env.nonce = 100000 + check hashAndPoW(env) == ("A788E02A95BFC673709E97CA81E39CA903BAD5638D3388964C51EB64952172D6", + 0.07692307692307693) + # PoW calculation with 8 leading zeroes + env.nonce = 276 + check hashAndPoW(env) == ("00E2374C6353C243E4073E209A7F2ACB2506522AF318B3B78CF9A88310A2A11C", + 19.692307692307693) + +suite "Whisper queue": + test "should throw out lower proof-of-work item when full": + var queue = initQueue(1) + + let msg0 = initMessage(env0) + let msg1 = initMessage(env1) + + discard queue.add(msg0) + discard queue.add(msg1) + + check: + queue.items.len() == 1 + queue.items[0].env.nonce == + (if msg0.pow > msg1.pow: msg0.env.nonce else: msg1.env.nonce) + + test "should not throw out messages as long as there is capacity": + var queue = initQueue(2) + + check: + queue.add(initMessage(env0)) == true + queue.add(initMessage(env1)) == true + + queue.items.len() == 2 + + test "check if order of queue is by decreasing PoW": + var queue = initQueue(3) + + let msg0 = initMessage(env0) + let msg1 = initMessage(env1) + let msg2 = initMessage(env2) + + discard queue.add(msg0) + discard queue.add(msg1) + discard queue.add(msg2) + + check: + queue.items.len() == 3 + queue.items[0].pow > queue.items[1].pow and + queue.items[1].pow > queue.items[2].pow + + test "check field order against expected rlp order": + check rlp.encode(env0) == + rlp.encodeList(env0.expiry, env0.ttl, env0.topic, env0.data, env0.nonce) + +# To test filters we do not care if the msg is valid or allowed +proc prepFilterTestMsg(pubKey = none[PublicKey](), symKey = none[SymKey](), + src = none[PrivateKey](), topic: Topic, + padding = none[seq[byte]]()): Message = + let payload = Payload(dst: pubKey, symKey: symKey, src: src, + payload: @[byte 0, 1, 2], padding: padding) + let encoded = whisper.encode(rng[], payload) + let env = Envelope(expiry: 1, ttl: 1, topic: topic, data: encoded.get(), + nonce: 0) + result = initMessage(env) + +suite "Whisper filter": + test "should notify filter on message with symmetric encryption": + var symKey: SymKey + let topic = [byte 0, 0, 0, 0] + let msg = prepFilterTestMsg(symKey = some(symKey), topic = topic) + + var filters = initTable[string, Filter]() + let filter = initFilter(symKey = some(symKey), topics = @[topic]) + let filterId = subscribeFilter(rng[], filters, filter) + + notify(filters, msg) + + let messages = filters.getFilterMessages(filterId) + check: + messages.len == 1 + messages[0].decoded.src.isNone() + messages[0].dst.isNone() + + test "should notify filter on message with asymmetric encryption": + let privKey = PrivateKey.random(rng[]) + let topic = [byte 0, 0, 0, 0] + let msg = prepFilterTestMsg(pubKey = some(privKey.toPublicKey()), + topic = topic) + + var filters = initTable[string, Filter]() + let filter = initFilter(privateKey = some(privKey), topics = @[topic]) + let filterId = subscribeFilter(rng[], filters, filter) + + notify(filters, msg) + + let messages = filters.getFilterMessages(filterId) + check: + messages.len == 1 + messages[0].decoded.src.isNone() + messages[0].dst.isSome() + + test "should notify filter on message with signature": + let privKey = PrivateKey.random(rng[]) + let topic = [byte 0, 0, 0, 0] + let msg = prepFilterTestMsg(src = some(privKey), topic = topic) + + var filters = initTable[string, Filter]() + let filter = initFilter(src = some(privKey.toPublicKey()), + topics = @[topic]) + let filterId = subscribeFilter(rng[], filters, filter) + + notify(filters, msg) + + let messages = filters.getFilterMessages(filterId) + check: + messages.len == 1 + messages[0].decoded.src.isSome() + messages[0].dst.isNone() + + test "test notify of filter against PoW requirement": + let topic = [byte 0, 0, 0, 0] + let padding = some(repeat(byte 0, 251)) + # this message has a PoW of 0.02962962962962963, number should be updated + # in case PoW algorithm changes or contents of padding, payload, topic, etc. + # update: now with NON rlp encoded envelope size the PoW of this message is + # 0.014492753623188406 + let msg = prepFilterTestMsg(topic = topic, padding = padding) + + var filters = initTable[string, Filter]() + let + filterId1 = subscribeFilter(rng[], filters, + initFilter(topics = @[topic], powReq = 0.014492753623188406)) + filterId2 = subscribeFilter(rng[], filters, + initFilter(topics = @[topic], powReq = 0.014492753623188407)) + + notify(filters, msg) + + check: + filters.getFilterMessages(filterId1).len == 1 + filters.getFilterMessages(filterId2).len == 0 + + test "test notify of filter on message with certain topic": + let + topic1 = [byte 0xAB, 0x12, 0xCD, 0x34] + topic2 = [byte 0, 0, 0, 0] + + let msg = prepFilterTestMsg(topic = topic1) + + var filters = initTable[string, Filter]() + let + filterId1 = subscribeFilter(rng[], filters, initFilter(topics = @[topic1])) + filterId2 = subscribeFilter(rng[], filters, initFilter(topics = @[topic2])) + + notify(filters, msg) + + check: + filters.getFilterMessages(filterId1).len == 1 + filters.getFilterMessages(filterId2).len == 0 diff --git a/tests/whisper/test_shh_config.nim b/tests/whisper/test_shh_config.nim new file mode 100644 index 000000000..5bb938314 --- /dev/null +++ b/tests/whisper/test_shh_config.nim @@ -0,0 +1,71 @@ +# +# Ethereum P2P +# (c) Copyright 2018-2021 +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +{.used.} + +import + std/[sequtils, options, unittest, times], + ../../waku/whisper/whisper_protocol as whisper + +suite "Whisper envelope validation": + test "should validate and allow envelope according to config": + let ttl = 1'u32 + let topic = [byte 1, 2, 3, 4] + let config = WhisperConfig(powRequirement: 0, bloom: topic.topicBloom(), + isLightNode: false, maxMsgSize: defaultMaxMsgSize) + + let env = Envelope(expiry:epochTime().uint32 + ttl, ttl: ttl, topic: topic, + data: repeat(byte 9, 256), nonce: 0) + check env.valid() + + let msg = initMessage(env) + check msg.allowed(config) + + test "should invalidate envelope due to ttl 0": + let ttl = 0'u32 + let topic = [byte 1, 2, 3, 4] + let config = WhisperConfig(powRequirement: 0, bloom: topic.topicBloom(), + isLightNode: false, maxMsgSize: defaultMaxMsgSize) + + let env = Envelope(expiry:epochTime().uint32 + ttl, ttl: ttl, topic: topic, + data: repeat(byte 9, 256), nonce: 0) + check env.valid() == false + + test "should invalidate envelope due to expired": + let ttl = 1'u32 + let topic = [byte 1, 2, 3, 4] + let config = WhisperConfig(powRequirement: 0, bloom: topic.topicBloom(), + isLightNode: false, maxMsgSize: defaultMaxMsgSize) + + let env = Envelope(expiry:epochTime().uint32, ttl: ttl, topic: topic, + data: repeat(byte 9, 256), nonce: 0) + check env.valid() == false + + test "should invalidate envelope due to in the future": + let ttl = 1'u32 + let topic = [byte 1, 2, 3, 4] + let config = WhisperConfig(powRequirement: 0, bloom: topic.topicBloom(), + isLightNode: false, maxMsgSize: defaultMaxMsgSize) + + # there is currently a 2 second tolerance, hence the + 3 + let env = Envelope(expiry:epochTime().uint32 + ttl + 3, ttl: ttl, topic: topic, + data: repeat(byte 9, 256), nonce: 0) + check env.valid() == false + + test "should not allow envelope due to bloom filter": + let topic = [byte 1, 2, 3, 4] + let wrongTopic = [byte 9, 8, 7, 6] + let config = WhisperConfig(powRequirement: 0, bloom: wrongTopic.topicBloom(), + isLightNode: false, maxMsgSize: defaultMaxMsgSize) + + let env = Envelope(expiry:100000 , ttl: 30, topic: topic, + data: repeat(byte 9, 256), nonce: 0) + + let msg = initMessage(env) + check msg.allowed(config) == false diff --git a/tests/whisper/test_shh_connect.nim b/tests/whisper/test_shh_connect.nim new file mode 100644 index 000000000..7ce035d07 --- /dev/null +++ b/tests/whisper/test_shh_connect.nim @@ -0,0 +1,329 @@ +# +# Ethereum P2P +# (c) Copyright 2018-2021 +# Status Research & Development GmbH +# +# Licensed under either of +# Apache License, version 2.0, (LICENSE-APACHEv2) +# MIT license (LICENSE-MIT) + +{.used.} + +import + std/[sequtils, options, tables], + chronos, testutils/unittests, bearssl, + eth/[keys, p2p], eth/p2p/peer_pool, + ../../waku/whisper/whisper_protocol as whisper, + ../test_helpers + +proc resetMessageQueues(nodes: varargs[EthereumNode]) = + for node in nodes: + node.resetMessageQueue() + +let safeTTL = 5'u32 +let waitInterval = messageInterval + 150.milliseconds + +procSuite "Whisper connections": + let rng = newRng() + var node1 = setupTestNode(rng, Whisper) + var node2 = setupTestNode(rng, Whisper) + node2.startListening() + waitFor node1.peerPool.connectToNode(newNode(node2.toENode())) + asyncTest "Two peers connected": + check: + node1.peerPool.connectedNodes.len() == 1 + + asyncTest "Filters with encryption and signing": + let encryptKeyPair = KeyPair.random(rng[]) + let signKeyPair = KeyPair.random(rng[]) + var symKey: SymKey + let topic = [byte 0x12, 0, 0, 0] + var filters: seq[string] = @[] + var payloads = [repeat(byte 1, 10), repeat(byte 2, 10), + repeat(byte 3, 10), repeat(byte 4, 10)] + var futures = [newFuture[int](), newFuture[int](), + newFuture[int](), newFuture[int]()] + + proc handler1(msg: ReceivedMessage) = + var count {.global.}: int + check msg.decoded.payload == payloads[0] or msg.decoded.payload == payloads[1] + count += 1 + if count == 2: futures[0].complete(1) + proc handler2(msg: ReceivedMessage) = + check msg.decoded.payload == payloads[1] + futures[1].complete(1) + proc handler3(msg: ReceivedMessage) = + var count {.global.}: int + check msg.decoded.payload == payloads[2] or msg.decoded.payload == payloads[3] + count += 1 + if count == 2: futures[2].complete(1) + proc handler4(msg: ReceivedMessage) = + check msg.decoded.payload == payloads[3] + futures[3].complete(1) + + # Filters + # filter for encrypted asym + filters.add(node1.subscribeFilter(initFilter(privateKey = some(encryptKeyPair.seckey), + topics = @[topic]), handler1)) + # filter for encrypted asym + signed + filters.add(node1.subscribeFilter(initFilter(some(signKeyPair.pubkey), + privateKey = some(encryptKeyPair.seckey), + topics = @[topic]), handler2)) + # filter for encrypted sym + filters.add(node1.subscribeFilter(initFilter(symKey = some(symKey), + topics = @[topic]), handler3)) + # filter for encrypted sym + signed + filters.add(node1.subscribeFilter(initFilter(some(signKeyPair.pubkey), + symKey = some(symKey), + topics = @[topic]), handler4)) + # Messages + check: + # encrypted asym + node2.postMessage(some(encryptKeyPair.pubkey), ttl = safeTTL, + topic = topic, payload = payloads[0]) == true + # encrypted asym + signed + node2.postMessage(some(encryptKeyPair.pubkey), + src = some(signKeyPair.seckey), ttl = safeTTL, + topic = topic, payload = payloads[1]) == true + # encrypted sym + node2.postMessage(symKey = some(symKey), ttl = safeTTL, topic = topic, + payload = payloads[2]) == true + # encrypted sym + signed + node2.postMessage(symKey = some(symKey), + src = some(signKeyPair.seckey), + ttl = safeTTL, topic = topic, + payload = payloads[3]) == true + + node2.protocolState(Whisper).queue.items.len == 4 + + check: + await allFutures(futures).withTimeout(waitInterval) + node1.protocolState(Whisper).queue.items.len == 4 + + for filter in filters: + check node1.unsubscribeFilter(filter) == true + + resetMessageQueues(node1, node2) + + asyncTest "Filters with topics": + let topic1 = [byte 0x12, 0, 0, 0] + let topic2 = [byte 0x34, 0, 0, 0] + var payloads = [repeat(byte 0, 10), repeat(byte 1, 10)] + var futures = [newFuture[int](), newFuture[int]()] + proc handler1(msg: ReceivedMessage) = + check msg.decoded.payload == payloads[0] + futures[0].complete(1) + proc handler2(msg: ReceivedMessage) = + check msg.decoded.payload == payloads[1] + futures[1].complete(1) + + var filter1 = node1.subscribeFilter(initFilter(topics = @[topic1]), handler1) + var filter2 = node1.subscribeFilter(initFilter(topics = @[topic2]), handler2) + + check: + node2.postMessage(ttl = safeTTL + 1, topic = topic1, + payload = payloads[0]) == true + node2.postMessage(ttl = safeTTL, topic = topic2, + payload = payloads[1]) == true + node2.protocolState(Whisper).queue.items.len == 2 + + await allFutures(futures).withTimeout(waitInterval) + node1.protocolState(Whisper).queue.items.len == 2 + + node1.unsubscribeFilter(filter1) == true + node1.unsubscribeFilter(filter2) == true + + resetMessageQueues(node1, node2) + + asyncTest "Filters with PoW": + let topic = [byte 0x12, 0, 0, 0] + var payload = repeat(byte 0, 10) + var futures = [newFuture[int](), newFuture[int]()] + proc handler1(msg: ReceivedMessage) = + check msg.decoded.payload == payload + futures[0].complete(1) + proc handler2(msg: ReceivedMessage) = + check msg.decoded.payload == payload + futures[1].complete(1) + + var filter1 = node1.subscribeFilter(initFilter(topics = @[topic], powReq = 0), + handler1) + var filter2 = node1.subscribeFilter(initFilter(topics = @[topic], + powReq = 1_000_000), handler2) + + check: + node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true + + (await futures[0].withTimeout(waitInterval)) == true + (await futures[1].withTimeout(waitInterval)) == false + node1.protocolState(Whisper).queue.items.len == 1 + + node1.unsubscribeFilter(filter1) == true + node1.unsubscribeFilter(filter2) == true + + resetMessageQueues(node1, node2) + + asyncTest "Filters with queues": + let topic = [byte 0, 0, 0, 0] + let payload = repeat(byte 0, 10) + + var filter = node1.subscribeFilter(initFilter(topics = @[topic])) + for i in countdown(10, 1): + check node2.postMessage(ttl = safeTTL, topic = topic, + payload = payload) == true + + await sleepAsync(waitInterval) + check: + node1.getFilterMessages(filter).len() == 10 + node1.getFilterMessages(filter).len() == 0 + node1.unsubscribeFilter(filter) == true + + resetMessageQueues(node1, node2) + + asyncTest "Local filter notify": + let topic = [byte 0, 0, 0, 0] + + var filter = node1.subscribeFilter(initFilter(topics = @[topic])) + check: + node1.postMessage(ttl = safeTTL, topic = topic, + payload = repeat(byte 4, 10)) == true + node1.getFilterMessages(filter).len() == 1 + node1.unsubscribeFilter(filter) == true + + await sleepAsync(waitInterval) + resetMessageQueues(node1, node2) + + asyncTest "Bloomfilter blocking": + let sendTopic1 = [byte 0x12, 0, 0, 0] + let sendTopic2 = [byte 0x34, 0, 0, 0] + let filterTopics = @[[byte 0x34, 0, 0, 0],[byte 0x56, 0, 0, 0]] + let payload = repeat(byte 0, 10) + var f: Future[int] = newFuture[int]() + proc handler(msg: ReceivedMessage) = + check msg.decoded.payload == payload + f.complete(1) + var filter = node1.subscribeFilter(initFilter(topics = filterTopics), handler) + await node1.setBloomFilter(node1.filtersToBloom()) + + check: + node2.postMessage(ttl = safeTTL, topic = sendTopic1, + payload = payload) == true + node2.protocolState(Whisper).queue.items.len == 1 + + (await f.withTimeout(waitInterval)) == false + node1.protocolState(Whisper).queue.items.len == 0 + + resetMessageQueues(node1, node2) + + f = newFuture[int]() + + check: + node2.postMessage(ttl = safeTTL, topic = sendTopic2, + payload = payload) == true + node2.protocolState(Whisper).queue.items.len == 1 + + await f.withTimeout(waitInterval) + f.read() == 1 + node1.protocolState(Whisper).queue.items.len == 1 + + node1.unsubscribeFilter(filter) == true + + await node1.setBloomFilter(fullBloom()) + + resetMessageQueues(node1, node2) + + asyncTest "PoW blocking": + let topic = [byte 0, 0, 0, 0] + let payload = repeat(byte 0, 10) + + await node1.setPowRequirement(1_000_000) + check: + node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true + node2.protocolState(Whisper).queue.items.len == 1 + await sleepAsync(waitInterval) + check: + node1.protocolState(Whisper).queue.items.len == 0 + + resetMessageQueues(node1, node2) + + await node1.setPowRequirement(0.0) + check: + node2.postMessage(ttl = safeTTL, topic = topic, payload = payload) == true + node2.protocolState(Whisper).queue.items.len == 1 + await sleepAsync(waitInterval) + check: + node1.protocolState(Whisper).queue.items.len == 1 + + resetMessageQueues(node1, node2) + + asyncTest "Queue pruning": + let topic = [byte 0, 0, 0, 0] + let payload = repeat(byte 0, 10) + # We need a minimum TTL of 2 as when set to 1 there is a small chance that + # it is already expired after messageInterval due to rounding down of float + # to uint32 in postMessage() + let lowerTTL = 2'u32 # Lower TTL as we need to wait for messages to expire + for i in countdown(10, 1): + check node2.postMessage(ttl = lowerTTL, topic = topic, payload = payload) == true + check node2.protocolState(Whisper).queue.items.len == 10 + + await sleepAsync(waitInterval) + check node1.protocolState(Whisper).queue.items.len == 10 + + await sleepAsync(milliseconds((lowerTTL+1)*1000)) + check node1.protocolState(Whisper).queue.items.len == 0 + check node2.protocolState(Whisper).queue.items.len == 0 + + resetMessageQueues(node1, node2) + + asyncTest "P2P post": + let topic = [byte 0, 0, 0, 0] + var f: Future[int] = newFuture[int]() + proc handler(msg: ReceivedMessage) = + check msg.decoded.payload == repeat(byte 4, 10) + f.complete(1) + + var filter = node1.subscribeFilter(initFilter(topics = @[topic], + allowP2P = true), handler) + check: + node1.setPeerTrusted(toNodeId(node2.keys.pubkey)) == true + node2.postMessage(ttl = 10, topic = topic, + payload = repeat(byte 4, 10), + targetPeer = some(toNodeId(node1.keys.pubkey))) == true + + await f.withTimeout(waitInterval) + f.read() == 1 + node1.protocolState(Whisper).queue.items.len == 0 + node2.protocolState(Whisper).queue.items.len == 0 + + node1.unsubscribeFilter(filter) == true + + asyncTest "Light node posting": + var ln1 = setupTestNode(rng, Whisper) + ln1.setLightNode(true) + + await ln1.peerPool.connectToNode(newNode(node2.toENode())) + + let topic = [byte 0, 0, 0, 0] + + check: + # normal post + ln1.postMessage(ttl = safeTTL, topic = topic, + payload = repeat(byte 0, 10)) == false + ln1.protocolState(Whisper).queue.items.len == 0 + # P2P post + ln1.postMessage(ttl = safeTTL, topic = topic, + payload = repeat(byte 0, 10), + targetPeer = some(toNodeId(node2.keys.pubkey))) == true + ln1.protocolState(Whisper).queue.items.len == 0 + + asyncTest "Connect two light nodes": + var ln1 = setupTestNode(rng, Whisper) + var ln2 = setupTestNode(rng, Whisper) + + ln1.setLightNode(true) + ln2.setLightNode(true) + + ln2.startListening() + let peer = await ln1.rlpxConnect(newNode(ln2.toENode())) + check peer.isNil == true diff --git a/waku.nimble b/waku.nimble index 4a4a1919b..26ee46f07 100644 --- a/waku.nimble +++ b/waku.nimble @@ -41,6 +41,10 @@ proc test(name: string, params = "-d:chronicles_log_level=DEBUG", lang = "c") = buildBinary name, "tests/", params exec "build/" & name +### Whisper tasks +task testwhisper, "Build & run Whisper tests": + test "all_tests_whisper", "-d:chronicles_log_level=WARN -d:chronosStrictException" + ### Waku v1 tasks task wakunode1, "Build Waku v1 cli node": buildBinary "wakunode1", "waku/v1/node/", @@ -94,4 +98,4 @@ task bridge, "Build Waku v1 - v2 bridge": task chat2bridge, "Build chat2-matterbridge": let name = "chat2bridge" - buildBinary name, "examples/v2/matterbridge/", "-d:chronicles_log_level=DEBUG" \ No newline at end of file + buildBinary name, "examples/v2/matterbridge/", "-d:chronicles_log_level=DEBUG" diff --git a/waku/v1/protocol/waku_bridge.nim b/waku/v1/protocol/waku_bridge.nim index 21ad2c659..bde679a37 100644 --- a/waku/v1/protocol/waku_bridge.nim +++ b/waku/v1/protocol/waku_bridge.nim @@ -12,7 +12,6 @@ import eth/p2p, - #eth/p2p/rlpx_protocols/whisper_protocol, ../../whisper/whisper_protocol, ./waku_protocol