Skip to content

Commit 6b75ad0

Browse files
committed
test: added test to check if nodes are properly added to the seed nodes when entering the network
This tests for if the Seed node contains the new nodes when they are created. It also checks if the new nodes discover each other after being created. Includes a change to `findNode`. It will no longer throw an error when failing to find the node. This will have to be thrown by the caller now. This was required by `refreshBucket` since it's very likely that we can't find the random node it is looking for.
1 parent 08beafc commit 6b75ad0

File tree

5 files changed

+110
-20
lines changed

5 files changed

+110
-20
lines changed

src/client/service/nodesFind.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { utils as grpcUtils } from '../../grpc';
77
import { validateSync, utils as validationUtils } from '../../validation';
88
import { matchSync } from '../../utils';
99
import * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb';
10+
import * as nodesErrors from '../../nodes/errors';
1011

1112
/**
1213
* Attempts to get the node address of a provided node ID (by contacting
@@ -44,6 +45,7 @@ function nodesFind({
4445
},
4546
);
4647
const address = await nodeConnectionManager.findNode(nodeId);
48+
if (address == null) throw new nodesErrors.ErrorNodeGraphNodeIdNotFound();
4749
response
4850
.setNodeId(nodesUtils.encodeNodeId(nodeId))
4951
.setAddress(

src/nodes/NodeConnectionManager.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@ class NodeConnectionManager {
315315
timer?: Timer,
316316
): Promise<ConnectionAndLock> {
317317
const targetAddress = await this.findNode(targetNodeId);
318+
if (targetAddress == null) {
319+
throw new nodesErrors.ErrorNodeGraphNodeIdNotFound();
320+
}
318321
// If the stored host is not a valid host (IP address), then we assume it to
319322
// be a hostname
320323
const targetHostname = !networkUtils.isHost(targetAddress.host)
@@ -447,23 +450,17 @@ class NodeConnectionManager {
447450
public async findNode(
448451
targetNodeId: NodeId,
449452
options: { signal?: AbortSignal } = {},
450-
): Promise<NodeAddress> {
453+
): Promise<NodeAddress | undefined> {
451454
const { signal } = { ...options };
452455
// First check if we already have an existing ID -> address record
453456
let address = (await this.nodeGraph.getNode(targetNodeId))?.address;
454457
// Otherwise, attempt to locate it by contacting network
455-
if (address == null) {
456-
address = await this.getClosestGlobalNodes(targetNodeId, undefined, {
458+
address =
459+
address ??
460+
(await this.getClosestGlobalNodes(targetNodeId, undefined, {
457461
signal,
458-
});
459-
// TODO: This currently just does one iteration
460-
// If not found in this single iteration, we throw an exception
461-
if (address == null) {
462-
throw new nodesErrors.ErrorNodeGraphNodeIdNotFound();
463-
}
464-
}
465-
// We ensure that we always return a NodeAddress (either by lookup, or
466-
// network search) - if we can't locate it from either, we throw an exception
462+
}));
463+
// TODO: This currently just does one iteration
467464
return address;
468465
}
469466

@@ -633,6 +630,7 @@ class NodeConnectionManager {
633630
*/
634631
@ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning())
635632
public async syncNodeGraph(block: boolean = true, timer?: Timer) {
633+
this.logger.info('Syncing nodeGraph');
636634
for (const seedNodeId of this.getSeedNodes()) {
637635
// Check if the connection is viable
638636
try {
@@ -646,6 +644,7 @@ class NodeConnectionManager {
646644
this.keyManager.getNodeId(),
647645
timer,
648646
);
647+
// FIXME: we need to ping a node before setting it
649648
for (const [nodeId, nodeData] of nodes) {
650649
if (!block) {
651650
this.queue.push(() =>

src/nodes/NodeManager.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ class NodeManager {
4747
protected refreshBucketQueue: Set<NodeBucketIndex> = new Set();
4848
protected refreshBucketQueueRunning: boolean = false;
4949
protected refreshBucketQueueRunner: Promise<void>;
50-
protected refreshBucketQueuePlug_: PromiseType<void>;
51-
protected refreshBucketQueueDrained_: PromiseType<void>;
50+
protected refreshBucketQueuePlug_: PromiseType<void> = promise();
51+
protected refreshBucketQueueDrained_: PromiseType<void> = promise();
5252
protected refreshBucketQueueAbortController: AbortController;
5353

5454
constructor({
@@ -109,7 +109,10 @@ class NodeManager {
109109
// We need to attempt a connection using the proxies
110110
// For now we will just do a forward connect + relay message
111111
const targetAddress =
112-
address ?? (await this.nodeConnectionManager.findNode(nodeId))!;
112+
address ?? (await this.nodeConnectionManager.findNode(nodeId));
113+
if (targetAddress == null) {
114+
throw new nodesErrors.ErrorNodeGraphNodeIdNotFound();
115+
}
113116
const targetHost = await networkUtils.resolveHost(targetAddress.host);
114117
return await this.nodeConnectionManager.pingNode(
115118
nodeId,

tests/nodes/NodeConnectionManager.general.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import Proxy from '@/network/Proxy';
1616

1717
import GRPCClientAgent from '@/agent/GRPCClientAgent';
1818
import * as nodesUtils from '@/nodes/utils';
19-
import * as nodesErrors from '@/nodes/errors';
2019
import * as keysUtils from '@/keys/utils';
2120
import * as grpcUtils from '@/grpc/utils';
2221
import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb';
@@ -336,9 +335,9 @@ describe(`${NodeConnectionManager.name} general test`, () => {
336335
port: 22222 as Port,
337336
} as NodeAddress);
338337
// Un-findable Node cannot be found
339-
await expect(() =>
340-
nodeConnectionManager.findNode(nodeId),
341-
).rejects.toThrowError(nodesErrors.ErrorNodeGraphNodeIdNotFound);
338+
await expect(nodeConnectionManager.findNode(nodeId)).resolves.toEqual(
339+
undefined,
340+
);
342341

343342
await server.stop();
344343
} finally {

tests/nodes/NodeConnectionManager.seednodes.test.ts

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { NodeId, SeedNodes } from '@/nodes/types';
1+
import type { NodeId, NodeIdEncoded, SeedNodes } from '@/nodes/types';
22
import type { Host, Port } from '@/network/types';
33
import type { Sigchain } from '@/sigchain';
44
import fs from 'fs';
@@ -123,6 +123,13 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => {
123123
});
124124

125125
beforeEach(async () => {
126+
// Clearing nodes from graphs
127+
for await (const [nodeId] of remoteNode1.nodeGraph.getNodes()) {
128+
await remoteNode1.nodeGraph.unsetNode(nodeId);
129+
}
130+
for await (const [nodeId] of remoteNode2.nodeGraph.getNodes()) {
131+
await remoteNode2.nodeGraph.unsetNode(nodeId);
132+
}
126133
dataDir = await fs.promises.mkdtemp(
127134
path.join(os.tmpdir(), 'polykey-test-'),
128135
);
@@ -417,4 +424,84 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => {
417424
await queue?.stop();
418425
}
419426
});
427+
test('should expand the network when nodes enter', async () => {
428+
// Using a single seed node we need to check that each entering node adds itself to the seed node.
429+
// Also need to check that the new nodes can be seen in the network.
430+
let node1: PolykeyAgent | undefined;
431+
let node2: PolykeyAgent | undefined;
432+
const seedNodes: SeedNodes = {};
433+
seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = {
434+
host: remoteNode1.proxy.getProxyHost(),
435+
port: remoteNode1.proxy.getProxyPort(),
436+
};
437+
seedNodes[nodesUtils.encodeNodeId(remoteNodeId2)] = {
438+
host: remoteNode2.proxy.getProxyHost(),
439+
port: remoteNode2.proxy.getProxyPort(),
440+
};
441+
try {
442+
logger.setLevel(LogLevel.WARN);
443+
node1 = await PolykeyAgent.createPolykeyAgent({
444+
nodePath: path.join(dataDir, 'node1'),
445+
password: 'password',
446+
networkConfig: {
447+
proxyHost: localHost,
448+
agentHost: localHost,
449+
clientHost: localHost,
450+
forwardHost: localHost,
451+
},
452+
seedNodes,
453+
logger,
454+
});
455+
node2 = await PolykeyAgent.createPolykeyAgent({
456+
nodePath: path.join(dataDir, 'node2'),
457+
password: 'password',
458+
networkConfig: {
459+
proxyHost: localHost,
460+
agentHost: localHost,
461+
clientHost: localHost,
462+
forwardHost: localHost,
463+
},
464+
seedNodes,
465+
logger,
466+
});
467+
468+
await node1.queue.drained();
469+
await node1.nodeManager.refreshBucketQueueDrained();
470+
await node2.queue.drained();
471+
await node2.nodeManager.refreshBucketQueueDrained();
472+
473+
const getAllNodes = async (node: PolykeyAgent) => {
474+
const nodes: Array<NodeIdEncoded> = [];
475+
for await (const [nodeId] of node.nodeGraph.getNodes()) {
476+
nodes.push(nodesUtils.encodeNodeId(nodeId));
477+
}
478+
return nodes;
479+
};
480+
const rNode1Nodes = await getAllNodes(remoteNode1);
481+
const rNode2Nodes = await getAllNodes(remoteNode2);
482+
const node1Nodes = await getAllNodes(node1);
483+
const node2Nodes = await getAllNodes(node2);
484+
485+
const nodeIdR1 = nodesUtils.encodeNodeId(remoteNodeId1);
486+
const nodeIdR2 = nodesUtils.encodeNodeId(remoteNodeId2);
487+
const nodeId1 = nodesUtils.encodeNodeId(node1.keyManager.getNodeId());
488+
const nodeId2 = nodesUtils.encodeNodeId(node2.keyManager.getNodeId());
489+
expect(rNode1Nodes).toContain(nodeId1);
490+
expect(rNode1Nodes).toContain(nodeId2);
491+
expect(rNode2Nodes).toContain(nodeId1);
492+
expect(rNode2Nodes).toContain(nodeId2);
493+
expect(node1Nodes).toContain(nodeIdR1);
494+
expect(node1Nodes).toContain(nodeIdR2);
495+
expect(node1Nodes).toContain(nodeId2);
496+
expect(node2Nodes).toContain(nodeIdR1);
497+
expect(node2Nodes).toContain(nodeIdR2);
498+
expect(node2Nodes).toContain(nodeId1);
499+
} finally {
500+
logger.setLevel(LogLevel.WARN);
501+
await node1?.stop();
502+
await node1?.destroy();
503+
await node2?.stop();
504+
await node2?.destroy();
505+
}
506+
});
420507
});

0 commit comments

Comments
 (0)