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

Add connectAndFetchPeers and new Discovery function Closes#1152 #1170

Merged

Conversation

ishantiw
Copy link
Contributor

@ishantiw ishantiw commented Mar 7, 2019

Description

Discovery should just take IP address and wPort and should be able to connect and fetch peer list independently of Peer object. This will remove the need to create Peer 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 from P2PPeerInfo ({ipAddress, wsPort}), this will assign an outbound socket and destroy it after fetching peers.

  • Update discovery function discoverPeer to use connectAndFetchPeers 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

…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
@ishantiw
Copy link
Contributor Author

ishantiw commented Mar 7, 2019

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.

@ishantiw
Copy link
Contributor Author

ishantiw commented Mar 11, 2019

I'm reverting back the changes related to the use of independent connectToFetchPeers call in discovery as it was it creating a lot of instability in tests. Since its making independent calls in some interval, the connection doesn't close properly sometimes. Also, in the beginning, if there are no peers in the peer list of any node in the network then it cannot fetch peers of each other and hence throws the error. I'm still keeping the fetch connectAndRequest to fetch status of seed peers and adding them to peer pool in the initial discovery process.

@ishantiw ishantiw marked this pull request as ready for review March 11, 2019 12:39
): Promise<ConnectAndFetchResponse> =>
new Promise<ConnectAndFetchResponse>(
(
resolve: (result: ConnectAndFetchResponse) => void,
Copy link
Contributor

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

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),
Copy link
Contributor

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

Copy link
Contributor Author

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

Promise.resolve(error),
);
outboundSocket.on('close', async () =>
Promise.resolve('Connection closed'),
Copy link
Contributor

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?

Copy link
Contributor Author

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

Promise.resolve('Connection closed'),
);
outboundSocket.on('connect', async () =>
Promise.resolve('Connection Successful'),
Copy link
Contributor

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...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

}
};

export const connectAndFetchStatus = async (
Copy link
Contributor

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?

Copy link
Contributor Author

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', () => {
Copy link
Contributor

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?

},
);

export const connectAndFetchPeers = async (
Copy link
Contributor

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...?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

const clientOptions: ClientOptionsUpdated = {
hostname: basicPeerInfo.ipAddress,
port: basicPeerInfo.wsPort,
query: querystring.stringify(nodeInfoForDiscovery),
Copy link

@jondubois jondubois Mar 11, 2019

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't tested it yet but I will cover this in #1175 as @shuse2 already pointed this out there

Copy link
Contributor Author

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.

}
};

export const connectAndFetchStatus = async (

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?

};
const seedPeerUpdatedInfos = await Promise.all(
seedPeers.map(async seedPeer =>
connectAndFetchStatus(seedPeer, this._nodeInfo, peerConfig),

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.

Copy link
Contributor Author

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 (

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.

Copy link
Contributor Author

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

);

if (socket) {
socket.destroy();

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).

peerConfig,
);
if (socket) {
socket.destroy();

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 (

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed as per discussion 👍

? 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 };

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.

Copy link
Contributor Author

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

@ishantiw ishantiw force-pushed the 1152_add_func_coonect_fetch_peers_and_new_discovery branch from ba6198c to ed34e03 Compare March 12, 2019 10:22
@ishantiw ishantiw force-pushed the 1152_add_func_coonect_fetch_peers_and_new_discovery branch from ed34e03 to 7574391 Compare March 12, 2019 10:23
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe remove this comment?

// 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)) {
Copy link
Contributor

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?

@@ -293,6 +337,11 @@ export class PeerPool extends EventEmitter {
if (!this.hasPeer(peerId)) {
this.addPeer(peerInfo);
}
const existingPeer = this.getPeer(peerId) as Peer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

const peersForDiscovery = knownPeers.map(peerInfo => {
const peerId = constructPeerIdFromPeerInfo(peerInfo);
if (this.hasPeer(peerId)) {
const existingPeer = this.getPeer(peerId) as Peer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as below

@@ -316,6 +316,13 @@ export class Peer extends EventEmitter {
this._outboundSocket.connect();
}

public createPeerFromOutboundConnection(

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.

): Promise<ReadonlyArray<P2PDiscoveredPeerInfo>> {
const seedPeerUpdatedInfosAndSocket = await Promise.all(
seedPeers.map(async seedPeer =>
connectAndFetchPeerInfo(seedPeer, nodeInfo, peerConfig),

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.

ackTimeout: this._peerPoolConfig.ackTimeout,
};
const peer = new Peer(peerInfo, peerConfig);
peer.createPeerFromOutboundConnection(socket);

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.

Copy link
Contributor

@shuse2 shuse2 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just small question

};
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.
Copy link
Contributor

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?

Copy link
Contributor Author

@ishantiw ishantiw Mar 14, 2019

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

@@ -713,6 +713,6 @@ export const connectAndFetchPeerInfo = async (

return { peerInfo, socket };
} catch (error) {
return error;
throw error;

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.

Copy link
Contributor Author

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

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.

@@ -253,33 +256,79 @@ export class PeerPool extends EventEmitter {
});
}

public async fetchStatusAndCreatePeer(

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.

@shuse2 shuse2 merged commit 07d8749 into release/2.1.0 Mar 14, 2019
@shuse2 shuse2 deleted the 1152_add_func_coonect_fetch_peers_and_new_discovery branch March 14, 2019 20:36
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants