Skip to content
This repository has been archived by the owner on Mar 26, 2019. It is now read-only.

Commit

Permalink
Untie filter code from EthereumNode + add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kdeme authored and zah committed Nov 27, 2018
1 parent bc9db02 commit 1e5eeec
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 25 deletions.
61 changes: 37 additions & 24 deletions eth_p2p/rlpx_protocols/shh_protocol.nim
Expand Up @@ -112,6 +112,8 @@ type
handler: Option[FilterMsgHandler]
queue: seq[ReceivedMessage]

Filters* = Table[string, Filter]

WhisperConfig* = object
powRequirement*: float64
bloom*: Bloom
Expand Down Expand Up @@ -558,7 +560,22 @@ proc newFilter*(src = none[PublicKey](), privateKey = none[PrivateKey](),
Filter(src: src, privateKey: privateKey, symKey: symKey, topics: topics,
powReq: powReq, allowP2P: allowP2P, bloom: toBloom(topics))

proc notify(filters: var Table[string, Filter], msg: Message) =
proc subscribeFilter*(filters: var Filters, filter: Filter,
handler = none[FilterMsgHandler]()): string =
# NOTE: Should we allow a filter without a key? Encryption is mandatory in v6?
# Check if asymmetric _and_ symmetric key? Now asymmetric just has precedence.
let id = generateRandomID()
var filter = filter
if handler.isSome():
filter.handler = handler
else:
filter.queue = newSeqOfCap[ReceivedMessage](defaultFilterQueueCapacity)

filters.add(id, filter)
debug "Filter added", filter = id
return id

proc notify*(filters: var Filters, msg: Message) =
var decoded: Option[DecodedPayload]
var keyHash: Hash

Expand Down Expand Up @@ -616,6 +633,19 @@ proc notify(filters: var Table[string, Filter], msg: Message) =
else:
filter.queue.insert(receivedMsg)

proc getFilterMessages*(filters: var Filters, filterId: string): seq[ReceivedMessage] =
result = @[]
if filters.contains(filterId):
if filters[filterId].handler.isNone():
result = filters[filterId].queue
filters[filterId].queue =
newSeqOfCap[ReceivedMessage](defaultFilterQueueCapacity)

proc toBloom*(filters: Filters): Bloom =
for filter in filters.values:
if filter.topics.len > 0:
result = result or filter.bloom

type
PeerState = ref object
initialized*: bool # when successfully completed the handshake
Expand All @@ -628,7 +658,7 @@ type

WhisperState = ref object
queue*: Queue
filters*: Table[string, Filter]
filters*: Filters
config*: WhisperConfig

proc run(peer: Peer) {.async.}
Expand Down Expand Up @@ -868,29 +898,17 @@ proc postMessage*(node: EthereumNode, pubKey = none[PublicKey](),

proc subscribeFilter*(node: EthereumNode, filter: Filter,
handler = none[FilterMsgHandler]()): string =
# NOTE: Should we allow a filter without a key? Encryption is mandatory in v6?
# Check if asymmetric _and_ symmetric key? Now asymmetric just has precedence.
let id = generateRandomID()
var filter = filter
if handler.isSome():
filter.handler = handler
else:
filter.queue = newSeqOfCap[ReceivedMessage](defaultFilterQueueCapacity)
node.protocolState(shh).filters.add(id, filter)
debug "Filter added", filter = id
return id
return node.protocolState(shh).filters.subscribeFilter(filter, handler)

proc unsubscribeFilter*(node: EthereumNode, filterId: string): bool =
var filter: Filter
return node.protocolState(shh).filters.take(filterId, filter)

proc getFilterMessages*(node: EthereumNode, filterId: string): seq[ReceivedMessage] =
result = @[]
if node.protocolState(shh).filters.contains(filterId):
if node.protocolState(shh).filters[filterId].handler.isNone():
result = node.protocolState(shh).filters[filterId].queue
node.protocolState(shh).filters[filterId].queue =
newSeqOfCap[ReceivedMessage](defaultFilterQueueCapacity)
return node.protocolState(shh).filters.getFilterMessages(filterId)

proc filtersToBloom*(node: EthereumNode): Bloom =
return node.protocolState(shh).filters.toBloom()

proc setPowRequirement*(node: EthereumNode, powReq: float64) {.async.} =
# NOTE: do we need a tolerance of old PoW for some time?
Expand All @@ -906,11 +924,6 @@ proc setBloomFilter*(node: EthereumNode, bloom: Bloom) {.async.} =
# asyncCheck peer.bloomFilterExchange(@bloom)
await peer.bloomFilterExchange(@bloom)

proc filtersToBloom*(node: EthereumNode): Bloom =
for filter in node.protocolState(shh).filters.values:
if filter.topics.len > 0:
result = result or filter.bloom

proc setMaxMessageSize*(node: EthereumNode, size: uint32): bool =
if size > defaultMaxMsgSize:
error "size > maxMsgSize"
Expand Down
94 changes: 93 additions & 1 deletion tests/tshh.nim
Expand Up @@ -8,7 +8,7 @@
# MIT license (LICENSE-MIT)

import
sequtils, options, unittest, times,
sequtils, options, unittest, times, tables,
nimcrypto/hash,
eth_keys, rlp,
eth_p2p/rlpx_protocols/shh_protocol as shh
Expand Down Expand Up @@ -274,3 +274,95 @@ suite "Whisper queue":
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): Message =
let payload = Payload(dst: pubKey, symKey: symKey, src: src,
payload: @[byte 0, 1, 2])
let encoded = shh.encode(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 = newFilter(symKey = some(symKey), topics = @[topic])
let filterId = filters.subscribeFilter(filter)

notify(filters, msg)

let messages = filters.getFilterMessages(filterId)
check messages.len == 1

test "should notify filter on message with asymmetric encryption":
let privKey = eth_keys.newPrivateKey()
let topic = [byte 0, 0, 0, 0]
let msg = prepFilterTestMsg(pubKey = some(privKey.getPublicKey()),
topic = topic)

var filters = initTable[string, Filter]()
let filter = newFilter(privateKey = some(privKey), topics = @[topic])
let filterId = filters.subscribeFilter(filter)

notify(filters, msg)

let messages = filters.getFilterMessages(filterId)
check messages.len == 1

test "should notify filter on message with signature":
let privKey = eth_keys.newPrivateKey()
let topic = [byte 0, 0, 0, 0]
let msg = prepFilterTestMsg(src = some(privKey), topic = topic)

var filters = initTable[string, Filter]()
let filter = newFilter(src = some(privKey.getPublicKey()),
topics = @[topic])
let filterId = filters.subscribeFilter(filter)

notify(filters, msg)

let messages = filters.getFilterMessages(filterId)
check messages.len == 1

test "test notify of filter against PoW requirement":
let topic = [byte 0, 0, 0, 0]
# this message has a PoW of 0.02962962962962963, number should be updated
# in case PoW algorithm changes
let msg = prepFilterTestMsg(topic = topic)

var filters = initTable[string, Filter]()
let
filterId1 = filters.subscribeFilter(
newFilter(topics = @[topic], powReq = 0.02962962962962963))
filterId2 = filters.subscribeFilter(
newFilter(topics = @[topic], powReq = 0.02962962962962964))

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 = filters.subscribeFilter(newFilter(topics = @[topic1]))
filterId2 = filters.subscribeFilter(newFilter(topics = @[topic2]))

notify(filters, msg)

check:
filters.getFilterMessages(filterId1).len == 1
filters.getFilterMessages(filterId2).len == 0

0 comments on commit 1e5eeec

Please sign in to comment.