mirror of
https://github.com/status-im/nim-eth.git
synced 2025-02-12 05:57:07 +00:00
Currently only setting `--styleCheck:hint` as there are some dependency fixes required and the compiler seems to trip over the findnode MessageKind, findnode Message field and the findNode proc. Also over protocol.Protocol usage.
384 lines
13 KiB
Nim
384 lines
13 KiB
Nim
#
|
|
# Ethereum P2P
|
|
# (c) Copyright 2018
|
|
# 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],
|
|
unittest2,
|
|
nimcrypto/hash,
|
|
../../eth/[keys, rlp],
|
|
../../eth/p2p/rlpx_protocols/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
|