-
Notifications
You must be signed in to change notification settings - Fork 64
Add connectAndFetchPeers and new Discovery function Closes#1152 #1170
Add connectAndFetchPeers and new Discovery function Closes#1152 #1170
Conversation
…P2PPeerInfo to do the probe
…nect_fetch_peers_and_new_discovery * improve_discovery_connect_fetch: ♻️ Add usage of new discovery process across p2p lib ♻️ Update exisiting discovery to use connectAndFetchPeers and accept P2PPeerInfo to do the probe 🌱 Add connectAndRequest to fetch peers and status independently
I have kept it in the draft state because there are a lot of changes that affect overall discovery, connecting with incoming connections and maintaining Peers in PeerPool. NOTE: Need to resolve a couple of tests related to stopping the node. I have skipped the partial network test (where nodes start at once without going into discovery) because it's not relevant anymore due to the change in initial discovery process. |
I'm reverting back the changes related to the use of independent |
packages/lisk-p2p/src/peer.ts
Outdated
): Promise<ConnectAndFetchResponse> => | ||
new Promise<ConnectAndFetchResponse>( | ||
( | ||
resolve: (result: ConnectAndFetchResponse) => void, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably no need to define type for resolve
and reject
here
packages/lisk-p2p/src/peer.ts
Outdated
const outboundSocket = socketClusterClient.create(clientOptions); | ||
// Attaching handlers for various events that could be used future for logging or any other application | ||
outboundSocket.on('error', async (error: Error) => | ||
Promise.resolve(error), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it should use the resolve
from input?
also i think this should be reject
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I was using it for debugging but forgot to change it, but now we don't need it as it will be handled within Peer object
packages/lisk-p2p/src/peer.ts
Outdated
Promise.resolve(error), | ||
); | ||
outboundSocket.on('close', async () => | ||
Promise.resolve('Connection closed'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above, also it should rejected with Error
object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be handled in Peer object now
packages/lisk-p2p/src/peer.ts
Outdated
Promise.resolve('Connection closed'), | ||
); | ||
outboundSocket.on('connect', async () => | ||
Promise.resolve('Connection Successful'), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we already resolve on connection...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
packages/lisk-p2p/src/peer.ts
Outdated
} | ||
}; | ||
|
||
export const connectAndFetchStatus = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this and fetchPeer happen on the separate socket?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we are reusing the socket and creating a peer object
@@ -72,7 +72,7 @@ describe('Integration tests for P2P library', () => { | |||
}); | |||
}); | |||
|
|||
describe('Partially connected network: All nodes launch at the same time. The seedPeers list of each node contains the next node in the sequence', () => { | |||
describe.skip('Partially connected network: All nodes launch at the same time. The seedPeers list of each node contains the next node in the sequence', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if it's not relevant anymore why not remove it?
packages/lisk-p2p/src/peer.ts
Outdated
}, | ||
); | ||
|
||
export const connectAndFetchPeers = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this function is not used...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed
packages/lisk-p2p/src/peer.ts
Outdated
const clientOptions: ClientOptionsUpdated = { | ||
hostname: basicPeerInfo.ipAddress, | ||
port: basicPeerInfo.wsPort, | ||
query: querystring.stringify(nodeInfoForDiscovery), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does querystring.stringify(...)
support nested objects (not just primitive types)? They should be allowed.
Maybe you can add a test case in the integration tests; maybe try adding a custom nested object to the nodeInfo
object passed to the constructor of P2P
instances for one of the test cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This issue will now be covered in this PR. Please ignore the previous comment.
packages/lisk-p2p/src/peer.ts
Outdated
} | ||
}; | ||
|
||
export const connectAndFetchStatus = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be renamed to connectAndFetchPeerInfo
?
packages/lisk-p2p/src/p2p.ts
Outdated
}; | ||
const seedPeerUpdatedInfos = await Promise.all( | ||
seedPeers.map(async seedPeer => | ||
connectAndFetchStatus(seedPeer, this._nodeInfo, peerConfig), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If connectAndFetchStatus
fails for some of the peers, we should log it but it shouldn't stop us from continuing with the discovery process (so long as if succeeded for some of the peers).
Currently, the Promise.all
call means that it's either all pass or all fail. If one of the seed peers is offline, our P2P node will fail to start completely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm returning the error instead of throwing and we can log or emit it if we want to
readonly socket?: SCClientSocket; | ||
} | ||
|
||
export const connectAndRequest = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
About connectAndRequest
: I think it would be safer if the socket was destroyed at the end of this function after it has fetched whatever data was required; that way the entire lifecycle of the socket will be encapsulated within this function and it avoids the possibility that someone will forget to destroy the socket after calling this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made the changes as discussed earlier
packages/lisk-p2p/src/peer.ts
Outdated
); | ||
|
||
if (socket) { | ||
socket.destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this can be moved inside the connectAndRequest
function (see previous comment).
packages/lisk-p2p/src/peer.ts
Outdated
peerConfig, | ||
); | ||
if (socket) { | ||
socket.destroy(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this can be moved inside the connectAndRequest
function (see previous comment).
@@ -18,6 +18,7 @@ import { constructPeerIdFromPeerInfo, Peer } from './peer'; | |||
export interface FilterPeerOptions { | |||
readonly blacklist: ReadonlyArray<string>; | |||
} | |||
|
|||
// TODO later: Implement LIPS to handle fixed and white list | |||
export const discoverPeers = async ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to completely stop using peer.fetchPeers()
for discovery or else we have two different discovery protocols. We should always use the new single-socket discovery approach.
It might seem more efficient to reuse the existing socket on the peer if it exists, but it's going to be a very difficult to manage these two different discovery approaches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed as per discussion 👍
packages/lisk-p2p/src/peer.ts
Outdated
? convertNodeInfoToLegacyFormat(nodeInfo) | ||
: undefined; | ||
// Add a new field discovery to tell the receiving side that the connection will be short lived | ||
const nodeInfoForDiscovery = { discovery: true, ...legacyNodeInfo }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This discovery: true
is good, but I don't see it being used on the receiving end of the connection when the connection
event triggers on the server.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field is no longer needed with the new approach
ba6198c
to
ed34e03
Compare
ed34e03
to
7574391
Compare
packages/lisk-p2p/src/p2p.ts
Outdated
if (!this._triedPeers.has(peerId)) { | ||
this._triedPeers.set(peerId, seedInfo); | ||
} | ||
}); | ||
// TODO: Once we will a new peer discovery then we can remove this typecasting to P2PDiscoveredPeerInfo |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe remove this comment?
packages/lisk-p2p/src/peer_pool.ts
Outdated
// Check for received discovery info and then find it in peer pool and then update it | ||
discoveredPeers.forEach((peerInfo: P2PDiscoveredPeerInfo) => { | ||
const peerId = constructPeerIdFromPeerInfo(peerInfo); | ||
if (this.hasPeer(peerId)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
instead of calling hasPeer
and casting getPeer
result, maybe just call getPeer
and check undefined?
packages/lisk-p2p/src/peer_pool.ts
Outdated
@@ -293,6 +337,11 @@ export class PeerPool extends EventEmitter { | |||
if (!this.hasPeer(peerId)) { | |||
this.addPeer(peerInfo); | |||
} | |||
const existingPeer = this.getPeer(peerId) as Peer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
packages/lisk-p2p/src/peer_pool.ts
Outdated
const peersForDiscovery = knownPeers.map(peerInfo => { | ||
const peerId = constructPeerIdFromPeerInfo(peerInfo); | ||
if (this.hasPeer(peerId)) { | ||
const existingPeer = this.getPeer(peerId) as Peer; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as below
packages/lisk-p2p/src/peer.ts
Outdated
@@ -316,6 +316,13 @@ export class Peer extends EventEmitter { | |||
this._outboundSocket.connect(); | |||
} | |||
|
|||
public createPeerFromOutboundConnection( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should pass the outbound socket to the peer's constructor as an optional argument instead of having a separate createPeerFromOutboundConnection
method. This is dangerous here because it could be called multiple times. If we move this logic to the constructor then we guarantee that this cannot happen.
The logic which instantiates the Peer
can just set the inboundSocket
as undefined
explicitly if there is no inbound socket.
packages/lisk-p2p/src/peer_pool.ts
Outdated
): Promise<ReadonlyArray<P2PDiscoveredPeerInfo>> { | ||
const seedPeerUpdatedInfosAndSocket = await Promise.all( | ||
seedPeers.map(async seedPeer => | ||
connectAndFetchPeerInfo(seedPeer, nodeInfo, peerConfig), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because connectAndFetchPeerInfo
is a pass or fail operation, it should throw; that way the function signature doesn't need to return dual types and we don't need to use instanceof
statement to separate the two outputs.
The fetchStatusAndCreatePeer
function can succeed even if it has errors, so returning the error should happen at this level. Maybe this function could return an object with an errors
and a peerInfoList
field. If no errors occurred, then the errors
field would be an empty array. Also, it doesn't seem that we are using the errors from connectAndFetchPeerInfo
anywhere at the moment (and this function is not returning them).
We should emit them on the top level P2P
instance using the existing EVENT_FAILED_TO_FETCH_PEER_INFO
event.
packages/lisk-p2p/src/peer_pool.ts
Outdated
ackTimeout: this._peerPoolConfig.ackTimeout, | ||
}; | ||
const peer = new Peer(peerInfo, peerConfig); | ||
peer.createPeerFromOutboundConnection(socket); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in a previous comment. It would be safer to pass the outbound socket to the Peer
constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just small question
packages/lisk-p2p/src/peer_pool.ts
Outdated
}; | ||
const peer = new Peer(peerInfo, peerConfig, { outbound: socket }); | ||
|
||
// Throw an error because adding a peer multiple times is a common developer error which is very difficult to itentify and debug. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where does it throw an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, it was a mistake, we don;t need to throw an error here as we are already checking above if it's present in peer pool or not. I corrected it
packages/lisk-p2p/src/peer.ts
Outdated
@@ -713,6 +713,6 @@ export const connectAndFetchPeerInfo = async ( | |||
|
|||
return { peerInfo, socket }; | |||
} catch (error) { | |||
return error; | |||
throw error; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the error is just being re-thrown as-is and we're not doing anything else with it here, we don't need the try-catch block in this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you commented on the outdated code, but now I'm throwing an error but capturing peer ip and port for better logging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It appears that you already fixed this in a later commit so ignore.
packages/lisk-p2p/src/peer_pool.ts
Outdated
@@ -253,33 +256,79 @@ export class PeerPool extends EventEmitter { | |||
}); | |||
} | |||
|
|||
public async fetchStatusAndCreatePeer( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method name should be plural because it's fetching and adding multiple peers.
Description
Discovery should just take
IP
address andwPort
and should be able to connect and fetch peer list independently ofPeer
object. This will remove the need to createPeer
objects for the seed peers just to fetch peer list from them. Also, it will be independent of the peer pool in terms of choosing peers with Peer objects already and could be handled independently.Add new
connectAndFetchPeers
method that will fetch all the peers fromP2PPeerInfo ({ipAddress, wsPort})
, this will assign an outbound socket and destroy it after fetching peers.Update discovery function
discoverPeer
to useconnectAndFetchPeers
to fetch peers from the peers passed as arguments.Update P2P
start()
method and subsequent_startDiscovery()
methods to use and accept new updated discovery method.Review checklist