mirror of https://github.com/waku-org/nwaku.git
Patch v0.5.1 - fix multiple protocol IDs in persistent storage (#687)
This commit is contained in:
parent
b4ee7071ba
commit
6b1e0c3079
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -1,5 +1,24 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2021-07-26 v0.5.1
|
||||||
|
|
||||||
|
This patch release contains the following fix:
|
||||||
|
- Support for multiple protocol IDs when reconnecting to previously connected peers:
|
||||||
|
A bug in `v0.5` caused clients using persistent peer storage to only support the mounted protocol ID.
|
||||||
|
|
||||||
|
This is a patch release that is fully backwards-compatible with release `v0.5`.
|
||||||
|
It supports the same [libp2p protocols](https://docs.libp2p.io/concepts/protocols/):
|
||||||
|
| Protocol | Spec status | Protocol id |
|
||||||
|
| ---: | :---: | :--- |
|
||||||
|
| [`17/WAKU-RLN`](https://rfc.vac.dev/spec/17/) | `raw` | `/vac/waku/waku-rln-relay/2.0.0-alpha1` |
|
||||||
|
| [`11/WAKU2-RELAY`](https://rfc.vac.dev/spec/11/) | `stable` | `/vac/waku/relay/2.0.0` |
|
||||||
|
| [`12/WAKU2-FILTER`](https://rfc.vac.dev/spec/12/) | `draft` | `/vac/waku/filter/2.0.0-beta1` |
|
||||||
|
| [`13/WAKU2-STORE`](https://rfc.vac.dev/spec/13/) | `draft` | `/vac/waku/store/2.0.0-beta3` |
|
||||||
|
| [`18/WAKU2-SWAP`](https://rfc.vac.dev/spec/18/) | `draft` | `/vac/waku/swap/2.0.0-beta1` |
|
||||||
|
| [`19/WAKU2-LIGHTPUSH`](https://rfc.vac.dev/spec/19/) | `draft` | `/vac/waku/lightpush/2.0.0-beta1` |
|
||||||
|
|
||||||
|
The Waku v1 implementation is stable but not under active development.
|
||||||
|
|
||||||
## 2021-07-23 v0.5
|
## 2021-07-23 v0.5
|
||||||
|
|
||||||
This release contains the following:
|
This release contains the following:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[options, sets, tables, sequtils],
|
std/[options, sets, tables, sequtils],
|
||||||
|
chronicles,
|
||||||
testutils/unittests, stew/shims/net as stewNet,
|
testutils/unittests, stew/shims/net as stewNet,
|
||||||
json_rpc/[rpcserver, rpcclient],
|
json_rpc/[rpcserver, rpcclient],
|
||||||
eth/[keys, rlp], eth/common/eth_types,
|
eth/[keys, rlp], eth/common/eth_types,
|
||||||
|
@ -164,7 +165,7 @@ procSuite "Peer Manager":
|
||||||
|
|
||||||
asyncTest "Peer manager can use persistent storage and survive restarts":
|
asyncTest "Peer manager can use persistent storage and survive restarts":
|
||||||
let
|
let
|
||||||
database = SqliteDatabase.init("", inMemory = true)[]
|
database = SqliteDatabase.init("1", inMemory = true)[]
|
||||||
storage = WakuPeerStorage.new(database)[]
|
storage = WakuPeerStorage.new(database)[]
|
||||||
nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||||
node1 = WakuNode.new(nodeKey1, ValidIpAddress.init("0.0.0.0"),
|
node1 = WakuNode.new(nodeKey1, ValidIpAddress.init("0.0.0.0"),
|
||||||
|
@ -209,3 +210,63 @@ procSuite "Peer Manager":
|
||||||
node3.peerManager.connectedness(peerInfo2.peerId) == Connected
|
node3.peerManager.connectedness(peerInfo2.peerId) == Connected
|
||||||
|
|
||||||
await allFutures([node1.stop(), node2.stop(), node3.stop()])
|
await allFutures([node1.stop(), node2.stop(), node3.stop()])
|
||||||
|
|
||||||
|
asyncTest "Peer manager support multiple protocol IDs when reconnecting to peers":
|
||||||
|
let
|
||||||
|
database = SqliteDatabase.init("2", inMemory = true)[]
|
||||||
|
storage = WakuPeerStorage.new(database)[]
|
||||||
|
nodeKey1 = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||||
|
node1 = WakuNode.new(nodeKey1, ValidIpAddress.init("0.0.0.0"),
|
||||||
|
Port(60000), peerStorage = storage)
|
||||||
|
nodeKey2 = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||||
|
node2 = WakuNode.new(nodeKey2, ValidIpAddress.init("0.0.0.0"),
|
||||||
|
Port(60002))
|
||||||
|
peerInfo2 = node2.peerInfo
|
||||||
|
betaCodec = "/vac/waku/relay/2.0.0-beta2"
|
||||||
|
stableCodec = "/vac/waku/relay/2.0.0"
|
||||||
|
|
||||||
|
await node1.start()
|
||||||
|
await node2.start()
|
||||||
|
|
||||||
|
node1.mountRelay()
|
||||||
|
node1.wakuRelay.codec = betaCodec
|
||||||
|
node2.mountRelay()
|
||||||
|
node2.wakuRelay.codec = betaCodec
|
||||||
|
|
||||||
|
discard await node1.peerManager.dialPeer(peerInfo2, node2.wakuRelay.codec, 2.seconds)
|
||||||
|
check:
|
||||||
|
# Currently connected to node2
|
||||||
|
node1.peerManager.peers().len == 1
|
||||||
|
node1.peerManager.peers().anyIt(it.peerId == peerInfo2.peerId)
|
||||||
|
node1.peerManager.peers().anyIt(it.protos.contains(node2.wakuRelay.codec))
|
||||||
|
node1.peerManager.connectedness(peerInfo2.peerId) == Connected
|
||||||
|
|
||||||
|
# Simulate restart by initialising a new node using the same storage
|
||||||
|
let
|
||||||
|
nodeKey3 = crypto.PrivateKey.random(Secp256k1, rng[])[]
|
||||||
|
node3 = WakuNode.new(nodeKey3, ValidIpAddress.init("0.0.0.0"),
|
||||||
|
Port(60004), peerStorage = storage)
|
||||||
|
|
||||||
|
node3.mountRelay()
|
||||||
|
node3.wakuRelay.codec = stableCodec
|
||||||
|
check:
|
||||||
|
# Node 2 and 3 have differing codecs
|
||||||
|
node2.wakuRelay.codec == betaCodec
|
||||||
|
node3.wakuRelay.codec == stableCodec
|
||||||
|
# Node2 has been loaded after "restart", but we have not yet reconnected
|
||||||
|
node3.peerManager.peers().len == 1
|
||||||
|
node3.peerManager.peers().anyIt(it.peerId == peerInfo2.peerId)
|
||||||
|
node3.peerManager.peers().anyIt(it.protos.contains(betaCodec))
|
||||||
|
node3.peerManager.connectedness(peerInfo2.peerId) == NotConnected
|
||||||
|
|
||||||
|
await node3.start() # This should trigger a reconnect
|
||||||
|
|
||||||
|
check:
|
||||||
|
# Reconnected to node2 after "restart"
|
||||||
|
node3.peerManager.peers().len == 1
|
||||||
|
node3.peerManager.peers().anyIt(it.peerId == peerInfo2.peerId)
|
||||||
|
node3.peerManager.peers().anyIt(it.protos.contains(betaCodec))
|
||||||
|
node3.peerManager.peers().anyIt(it.protos.contains(stableCodec))
|
||||||
|
node3.peerManager.connectedness(peerInfo2.peerId) == Connected
|
||||||
|
|
||||||
|
await allFutures([node1.stop(), node2.stop(), node3.stop()])
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import
|
import
|
||||||
std/[options, sets, sequtils, times],
|
std/[options, sets, sequtils, times],
|
||||||
chronos, chronicles, metrics,
|
chronos, chronicles, metrics,
|
||||||
|
libp2p/multistream,
|
||||||
./waku_peer_store,
|
./waku_peer_store,
|
||||||
../storage/peer/peer_storage
|
../storage/peer/peer_storage
|
||||||
|
|
||||||
|
@ -78,8 +79,11 @@ proc dialPeer(pm: PeerManager, peerId: PeerID,
|
||||||
return none(Connection)
|
return none(Connection)
|
||||||
|
|
||||||
proc loadFromStorage(pm: PeerManager) =
|
proc loadFromStorage(pm: PeerManager) =
|
||||||
|
debug "loading peers from storage"
|
||||||
# Load peers from storage, if available
|
# Load peers from storage, if available
|
||||||
proc onData(peerId: PeerID, storedInfo: StoredInfo, connectedness: Connectedness, disconnectTime: int64) =
|
proc onData(peerId: PeerID, storedInfo: StoredInfo, connectedness: Connectedness, disconnectTime: int64) =
|
||||||
|
trace "loading peer", peerId=peerId, storedInfo=storedInfo, connectedness=connectedness
|
||||||
|
|
||||||
if peerId == pm.switch.peerInfo.peerId:
|
if peerId == pm.switch.peerInfo.peerId:
|
||||||
# Do not manage self
|
# Do not manage self
|
||||||
return
|
return
|
||||||
|
@ -94,6 +98,8 @@ proc loadFromStorage(pm: PeerManager) =
|
||||||
if res.isErr:
|
if res.isErr:
|
||||||
warn "failed to load peers from storage", err = res.error
|
warn "failed to load peers from storage", err = res.error
|
||||||
waku_peers_errors.inc(labelValues = ["storage_load_failure"])
|
waku_peers_errors.inc(labelValues = ["storage_load_failure"])
|
||||||
|
else:
|
||||||
|
debug "successfully queried peer storage"
|
||||||
|
|
||||||
##################
|
##################
|
||||||
# Initialisation #
|
# Initialisation #
|
||||||
|
@ -116,6 +122,8 @@ proc new*(T: type PeerManager, switch: Switch, storage: PeerStorage = nil): Peer
|
||||||
let pm = PeerManager(switch: switch,
|
let pm = PeerManager(switch: switch,
|
||||||
peerStore: WakuPeerStore.new(),
|
peerStore: WakuPeerStore.new(),
|
||||||
storage: storage)
|
storage: storage)
|
||||||
|
|
||||||
|
debug "creating new PeerManager"
|
||||||
|
|
||||||
proc peerHook(peerInfo: PeerInfo, event: ConnEvent): Future[void] {.gcsafe.} =
|
proc peerHook(peerInfo: PeerInfo, event: ConnEvent): Future[void] {.gcsafe.} =
|
||||||
onConnEvent(pm, peerInfo.peerId, event)
|
onConnEvent(pm, peerInfo.peerId, event)
|
||||||
|
@ -124,7 +132,10 @@ proc new*(T: type PeerManager, switch: Switch, storage: PeerStorage = nil): Peer
|
||||||
pm.switch.addConnEventHandler(peerHook, ConnEventKind.Disconnected)
|
pm.switch.addConnEventHandler(peerHook, ConnEventKind.Disconnected)
|
||||||
|
|
||||||
if not storage.isNil:
|
if not storage.isNil:
|
||||||
|
debug "found persistent peer storage"
|
||||||
pm.loadFromStorage() # Load previously managed peers.
|
pm.loadFromStorage() # Load previously managed peers.
|
||||||
|
else:
|
||||||
|
debug "no peer storage found"
|
||||||
|
|
||||||
return pm
|
return pm
|
||||||
|
|
||||||
|
@ -140,6 +151,10 @@ proc peers*(pm: PeerManager, proto: string): seq[StoredInfo] =
|
||||||
# Return the known info for all peers registered on the specified protocol
|
# Return the known info for all peers registered on the specified protocol
|
||||||
pm.peers.filterIt(it.protos.contains(proto))
|
pm.peers.filterIt(it.protos.contains(proto))
|
||||||
|
|
||||||
|
proc peers*(pm: PeerManager, protocolMatcher: Matcher): seq[StoredInfo] =
|
||||||
|
# Return the known info for all peers matching the provided protocolMatcher
|
||||||
|
pm.peers.filter(proc (storedInfo: StoredInfo): bool = storedInfo.protos.anyIt(protocolMatcher(it)))
|
||||||
|
|
||||||
proc connectedness*(pm: PeerManager, peerId: PeerId): Connectedness =
|
proc connectedness*(pm: PeerManager, peerId: PeerId): Connectedness =
|
||||||
# Return the connection state of the given, managed peer
|
# Return the connection state of the given, managed peer
|
||||||
# @TODO the PeerManager should keep and update local connectedness state for peers, redial on disconnect, etc.
|
# @TODO the PeerManager should keep and update local connectedness state for peers, redial on disconnect, etc.
|
||||||
|
@ -162,6 +177,10 @@ proc hasPeers*(pm: PeerManager, proto: string): bool =
|
||||||
# Returns `true` if manager has any peers for the specified protocol
|
# Returns `true` if manager has any peers for the specified protocol
|
||||||
pm.peers.anyIt(it.protos.contains(proto))
|
pm.peers.anyIt(it.protos.contains(proto))
|
||||||
|
|
||||||
|
proc hasPeers*(pm: PeerManager, protocolMatcher: Matcher): bool =
|
||||||
|
# Returns `true` if manager has any peers matching the protocolMatcher
|
||||||
|
pm.peers.any(proc (storedInfo: StoredInfo): bool = storedInfo.protos.anyIt(protocolMatcher(it)))
|
||||||
|
|
||||||
proc addPeer*(pm: PeerManager, peerInfo: PeerInfo, proto: string) =
|
proc addPeer*(pm: PeerManager, peerInfo: PeerInfo, proto: string) =
|
||||||
# Adds peer to manager for the specified protocol
|
# Adds peer to manager for the specified protocol
|
||||||
|
|
||||||
|
@ -200,13 +219,16 @@ proc selectPeer*(pm: PeerManager, proto: string): Option[PeerInfo] =
|
||||||
else:
|
else:
|
||||||
return none(PeerInfo)
|
return none(PeerInfo)
|
||||||
|
|
||||||
proc reconnectPeers*(pm: PeerManager, proto: string, backoff: chronos.Duration = chronos.seconds(0)) {.async.} =
|
proc reconnectPeers*(pm: PeerManager,
|
||||||
|
proto: string,
|
||||||
|
protocolMatcher: Matcher,
|
||||||
|
backoff: chronos.Duration = chronos.seconds(0)) {.async.} =
|
||||||
## Reconnect to peers registered for this protocol. This will update connectedness.
|
## Reconnect to peers registered for this protocol. This will update connectedness.
|
||||||
## Especially useful to resume connections from persistent storage after a restart.
|
## Especially useful to resume connections from persistent storage after a restart.
|
||||||
|
|
||||||
debug "Reconnecting peers", proto=proto
|
debug "Reconnecting peers", proto=proto
|
||||||
|
|
||||||
for storedInfo in pm.peers(proto):
|
for storedInfo in pm.peers(protocolMatcher):
|
||||||
# Check if peer is reachable.
|
# Check if peer is reachable.
|
||||||
if pm.peerStore.connectionBook.get(storedInfo.peerId) == CannotConnect:
|
if pm.peerStore.connectionBook.get(storedInfo.peerId) == CannotConnect:
|
||||||
debug "Not reconnecting to unreachable peer", peerId=storedInfo.peerId
|
debug "Not reconnecting to unreachable peer", peerId=storedInfo.peerId
|
||||||
|
@ -224,6 +246,12 @@ proc reconnectPeers*(pm: PeerManager, proto: string, backoff: chronos.Duration =
|
||||||
debug "Backing off before reconnect...", peerId=storedInfo.peerId, backoffTime=backoffTime
|
debug "Backing off before reconnect...", peerId=storedInfo.peerId, backoffTime=backoffTime
|
||||||
# We disconnected recently and still need to wait for a backoff period before connecting
|
# We disconnected recently and still need to wait for a backoff period before connecting
|
||||||
await sleepAsync(backoffTime)
|
await sleepAsync(backoffTime)
|
||||||
|
|
||||||
|
# Add to protos for peer, if it has not been added yet
|
||||||
|
if not pm.peerStore.get(storedInfo.peerId).protos.contains(proto):
|
||||||
|
let peerInfo = storedInfo.toPeerInfo()
|
||||||
|
trace "Adding newly dialed peer to manager", peerId = peerInfo.peerId, addr = peerInfo.addrs[0], proto = proto
|
||||||
|
pm.addPeer(peerInfo, proto)
|
||||||
|
|
||||||
trace "Reconnecting to peer", peerId=storedInfo.peerId
|
trace "Reconnecting to peer", peerId=storedInfo.peerId
|
||||||
discard await pm.dialPeer(storedInfo.peerId, toSeq(storedInfo.addrs), proto)
|
discard await pm.dialPeer(storedInfo.peerId, toSeq(storedInfo.addrs), proto)
|
||||||
|
|
|
@ -80,15 +80,16 @@ type
|
||||||
rng*: ref BrHmacDrbgContext
|
rng*: ref BrHmacDrbgContext
|
||||||
started*: bool # Indicates that node has started listening
|
started*: bool # Indicates that node has started listening
|
||||||
|
|
||||||
func protocolMatcher(codec: string): Matcher =
|
proc protocolMatcher(codec: string): Matcher =
|
||||||
## Returns a protocol matcher function for the provided codec
|
## Returns a protocol matcher function for the provided codec
|
||||||
|
|
||||||
proc match(proto: string): bool {.gcsafe.} =
|
proc match(proto: string): bool {.gcsafe.} =
|
||||||
## Matches a proto with any postfix to the provided codec.
|
## Matches a proto with any postfix to the provided codec.
|
||||||
## E.g. if the codec is `/vac/waku/filter/2.0.0` it matches the protos:
|
## E.g. if the codec is `/vac/waku/filter/2.0.0` it matches the protos:
|
||||||
## `/vac/waku/filter/2.0.0`, `/vac/waku/filter/2.0.0-beta3`, `/vac/waku/filter/2.0.0-actualnonsense`
|
## `/vac/waku/filter/2.0.0`, `/vac/waku/filter/2.0.0-beta3`, `/vac/waku/filter/2.0.0-actualnonsense`
|
||||||
return proto.startsWith(codec)
|
return proto.startsWith(codec)
|
||||||
|
|
||||||
|
return match
|
||||||
|
|
||||||
proc removeContentFilters(filters: var Filters, contentFilters: seq[ContentFilter]) {.gcsafe.} =
|
proc removeContentFilters(filters: var Filters, contentFilters: seq[ContentFilter]) {.gcsafe.} =
|
||||||
# Flatten all unsubscribe topics into single seq
|
# Flatten all unsubscribe topics into single seq
|
||||||
let unsubscribeTopics = contentFilters.mapIt(it.contentTopic)
|
let unsubscribeTopics = contentFilters.mapIt(it.contentTopic)
|
||||||
|
@ -461,13 +462,14 @@ proc startRelay*(node: WakuNode) {.async.} =
|
||||||
node.subscribe(topic, none(TopicHandler))
|
node.subscribe(topic, none(TopicHandler))
|
||||||
|
|
||||||
# Resume previous relay connections
|
# Resume previous relay connections
|
||||||
if node.peerManager.hasPeers(WakuRelayCodec):
|
if node.peerManager.hasPeers(protocolMatcher(WakuRelayCodec)):
|
||||||
info "Found previous WakuRelay peers. Reconnecting."
|
info "Found previous WakuRelay peers. Reconnecting."
|
||||||
|
|
||||||
# Reconnect to previous relay peers. This will respect a backoff period, if necessary
|
# Reconnect to previous relay peers. This will respect a backoff period, if necessary
|
||||||
let backoffPeriod = node.wakuRelay.parameters.pruneBackoff + chronos.seconds(BackoffSlackTime)
|
let backoffPeriod = node.wakuRelay.parameters.pruneBackoff + chronos.seconds(BackoffSlackTime)
|
||||||
|
|
||||||
await node.peerManager.reconnectPeers(WakuRelayCodec,
|
await node.peerManager.reconnectPeers(WakuRelayCodec,
|
||||||
|
protocolMatcher(WakuRelayCodec),
|
||||||
backoffPeriod)
|
backoffPeriod)
|
||||||
|
|
||||||
when defined(rln):
|
when defined(rln):
|
||||||
|
|
Loading…
Reference in New Issue