From a522de3be06ae5b4b871c0f83ef1ee49f6865879 Mon Sep 17 00:00:00 2001 From: Brian Botha Date: Tue, 7 Jun 2022 16:59:35 +1000 Subject: [PATCH] fix: `NodeConnectionManager.syncNodeGraph` now pings nodes Need to ensure validity of nodes by pinging them before adding them to the node graph. #322 --- src/bin/nodes/CommandAdd.ts | 12 +- src/bin/utils/options.ts | 11 + src/client/service/nodesAdd.ts | 22 +- src/nodes/NodeConnectionManager.ts | 64 +++-- src/nodes/NodeManager.ts | 6 + src/nodes/errors.ts | 6 + src/proto/js/google/protobuf/any_pb.js | 1 + src/proto/js/google/protobuf/descriptor_pb.js | 1 + src/proto/js/google/protobuf/duration_pb.js | 1 + src/proto/js/google/protobuf/empty_pb.js | 1 + src/proto/js/google/protobuf/field_mask_pb.js | 1 + src/proto/js/google/protobuf/struct_pb.js | 1 + src/proto/js/google/protobuf/timestamp_pb.js | 1 + src/proto/js/google/protobuf/wrappers_pb.js | 1 + src/proto/js/polykey/v1/agent/agent_pb.js | 1 + src/proto/js/polykey/v1/agent_service_pb.js | 1 + .../js/polykey/v1/client_service_grpc_pb.d.ts | 20 +- .../js/polykey/v1/client_service_grpc_pb.js | 17 +- src/proto/js/polykey/v1/client_service_pb.js | 1 + .../js/polykey/v1/gestalts/gestalts_pb.js | 1 + .../js/polykey/v1/identities/identities_pb.js | 1 + src/proto/js/polykey/v1/keys/keys_pb.js | 1 + src/proto/js/polykey/v1/nodes/nodes_pb.d.ts | 32 +++ src/proto/js/polykey/v1/nodes/nodes_pb.js | 264 ++++++++++++++++++ .../v1/notifications/notifications_pb.js | 1 + .../polykey/v1/permissions/permissions_pb.js | 1 + src/proto/js/polykey/v1/secrets/secrets_pb.js | 1 + .../js/polykey/v1/sessions/sessions_pb.js | 1 + src/proto/js/polykey/v1/test_service_pb.js | 1 + src/proto/js/polykey/v1/utils/utils_pb.js | 1 + src/proto/js/polykey/v1/vaults/vaults_pb.js | 1 + .../schemas/polykey/v1/client_service.proto | 2 +- .../schemas/polykey/v1/nodes/nodes.proto | 7 + tests/bin/nodes/add.test.ts | 98 ++++++- tests/bin/nodes/find.test.ts | 60 ++-- tests/client/service/nodesAdd.test.ts | 8 +- .../NodeConnectionManager.general.test.ts | 35 ++- .../NodeConnectionManager.seednodes.test.ts | 38 ++- 38 files changed, 639 insertions(+), 84 deletions(-) diff --git a/src/bin/nodes/CommandAdd.ts b/src/bin/nodes/CommandAdd.ts index fdf49f48e..49ea3105a 100644 --- a/src/bin/nodes/CommandAdd.ts +++ b/src/bin/nodes/CommandAdd.ts @@ -18,6 +18,8 @@ class CommandAdd extends CommandPolykey { this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); + this.addOption(binOptions.forceNodeAdd); + this.addOption(binOptions.noPing); this.action(async (nodeId: NodeId, host: Host, port: Port, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const nodesUtils = await import('../../nodes/utils'); @@ -46,13 +48,15 @@ class CommandAdd extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - const nodeAddressMessage = new nodesPB.NodeAddress(); - nodeAddressMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); - nodeAddressMessage.setAddress( + const nodeAddMessage = new nodesPB.NodeAdd(); + nodeAddMessage.setNodeId(nodesUtils.encodeNodeId(nodeId)); + nodeAddMessage.setAddress( new nodesPB.Address().setHost(host).setPort(port), ); + nodeAddMessage.setForce(options.force); + nodeAddMessage.setPing(options.ping); await binUtils.retryAuthentication( - (auth) => pkClient.grpcClient.nodesAdd(nodeAddressMessage, auth), + (auth) => pkClient.grpcClient.nodesAdd(nodeAddMessage, auth), meta, ); } finally { diff --git a/src/bin/utils/options.ts b/src/bin/utils/options.ts index bed18d65a..f2da17b8c 100644 --- a/src/bin/utils/options.ts +++ b/src/bin/utils/options.ts @@ -154,6 +154,15 @@ const pullVault = new commander.Option( 'Name or Id of the vault to pull from', ); +const forceNodeAdd = new commander.Option( + '--force', + 'Force adding node to nodeGraph', +).default(false); + +const noPing = new commander.Option('--no-ping', 'Skip ping step').default( + true, +); + export { nodePath, format, @@ -176,4 +185,6 @@ export { network, workers, pullVault, + forceNodeAdd, + noPing, }; diff --git a/src/client/service/nodesAdd.ts b/src/client/service/nodesAdd.ts index 0d993c746..3b6043219 100644 --- a/src/client/service/nodesAdd.ts +++ b/src/client/service/nodesAdd.ts @@ -6,6 +6,7 @@ import type { NodeId, NodeAddress } from '../../nodes/types'; import type { Host, Hostname, Port } from '../../network/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; import type Logger from '@matrixai/logger'; +import * as nodeErrors from '../../nodes/errors'; import * as grpcUtils from '../../grpc/utils'; import { validateSync } from '../../validation'; import * as validationUtils from '../../validation/utils'; @@ -30,12 +31,13 @@ function nodesAdd({ logger: Logger; }) { return async ( - call: grpc.ServerUnaryCall, + call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { try { const response = new utilsPB.EmptyMessage(); const metadata = await authenticate(call.metadata); + const request = call.request; call.sendMetadata(metadata); const { nodeId, @@ -55,11 +57,21 @@ function nodesAdd({ ); }, { - nodeId: call.request.getNodeId(), - host: call.request.getAddress()?.getHost(), - port: call.request.getAddress()?.getPort(), + nodeId: request.getNodeId(), + host: request.getAddress()?.getHost(), + port: request.getAddress()?.getPort(), }, ); + // Pinging to authenticate the node + if ( + request.getPing() && + !(await nodeManager.pingNode(nodeId, { host, port })) + ) { + throw new nodeErrors.ErrorNodePingFailed( + 'Failed to authenticate target node', + ); + } + await db.withTransactionF(async (tran) => nodeManager.setNode( nodeId, @@ -68,7 +80,7 @@ function nodesAdd({ port, } as NodeAddress, true, - true, + request.getForce(), undefined, tran, ), diff --git a/src/nodes/NodeConnectionManager.ts b/src/nodes/NodeConnectionManager.ts index 093fe22d6..f39f333d8 100644 --- a/src/nodes/NodeConnectionManager.ts +++ b/src/nodes/NodeConnectionManager.ts @@ -111,7 +111,12 @@ class NodeConnectionManager { this.nodeManager = nodeManager; for (const nodeIdEncoded in this.seedNodes) { const nodeId = nodesUtils.decodeNodeId(nodeIdEncoded)!; - await this.nodeGraph.setNode(nodeId, this.seedNodes[nodeIdEncoded]); + await this.nodeManager.setNode( + nodeId, + this.seedNodes[nodeIdEncoded], + true, + true, + ); } this.logger.info(`Started ${this.constructor.name}`); } @@ -431,10 +436,14 @@ class NodeConnectionManager { timer?: Timer, options: { signal?: AbortSignal } = {}, ): Promise { + const localNodeId = this.keyManager.getNodeId(); const { signal } = { ...options }; // Let foundTarget: boolean = false; let foundAddress: NodeAddress | undefined = undefined; // Get the closest alpha nodes to the target node (set as shortlist) + // FIXME? this is an array. Shouldn't it be a set? + // It's possible for this to grow faster than we can consume it, + // doubly so if we allow duplicates const shortlist = await this.nodeGraph.getClosestNodes( targetNodeId, this.initialClosestNodes, @@ -466,13 +475,15 @@ class NodeConnectionManager { } // Connect to the node (check if pre-existing connection exists, otherwise // create a new one) - try { - // Add the node to the database so that we can find its address in - // call to getConnectionToNode - await this.nodeGraph.setNode(nextNodeId, nextNodeAddress.address); - await this.getConnection(nextNodeId, timer); - } catch (e) { - // If we can't connect to the node, then skip it + if ( + await this.pingNode( + nextNodeId, + nextNodeAddress.address.host, + nextNodeAddress.address.port, + ) + ) { + await this.nodeManager!.setNode(nextNodeId, nextNodeAddress.address); + } else { continue; } contacted[nextNodeId] = true; @@ -486,12 +497,19 @@ class NodeConnectionManager { // them to the shortlist for (const [nodeId, nodeData] of foundClosest) { if (signal?.aborted) throw new nodesErrors.ErrorNodeAborted(); - // Ignore a`ny nodes that have been contacted - if (contacted[nodeId]) { + // Ignore any nodes that have been contacted or our own node + if (contacted[nodeId] || localNodeId.equals(nodeId)) { continue; } - if (nodeId.equals(targetNodeId)) { - await this.nodeGraph.setNode(nodeId, nodeData.address); + if ( + nodeId.equals(targetNodeId) && + (await this.pingNode( + nodeId, + nodeData.address.host, + nodeData.address.port, + )) + ) { + await this.nodeManager!.setNode(nodeId, nodeData.address); foundAddress = nodeData.address; // We have found the target node, so we can stop trying to look for it // in the shortlist @@ -555,7 +573,9 @@ class NodeConnectionManager { host: address.getHost() as Host | Hostname, port: address.getPort() as Port, }, - lastUpdated: 0, // FIXME? + // Not really needed + // But if it's needed then we need to add the information to the proto definition + lastUpdated: 0, }, ]); } @@ -589,15 +609,20 @@ class NodeConnectionManager { this.keyManager.getNodeId(), timer, ); - // FIXME: we need to ping a node before setting it for (const [nodeId, nodeData] of nodes) { + const pingAndAddNode = async () => { + const port = nodeData.address.port; + const host = await networkUtils.resolveHost(nodeData.address.host); + if (await this.pingNode(nodeId, host, port)) { + await this.nodeManager!.setNode(nodeId, nodeData.address, true); + } + }; + if (!block) { - this.queue.push(() => - this.nodeManager!.setNode(nodeId, nodeData.address), - ); + this.queue.push(pingAndAddNode); } else { try { - await this.nodeManager?.setNode(nodeId, nodeData.address); + await pingAndAddNode(); } catch (e) { if (!(e instanceof nodesErrors.ErrorNodeGraphSameNodeId)) throw e; } @@ -703,10 +728,11 @@ class NodeConnectionManager { @ready(new nodesErrors.ErrorNodeConnectionManagerNotRunning()) public async pingNode( nodeId: NodeId, - host: Host, + host: Host | Hostname, port: Port, timer?: Timer, ): Promise { + host = await networkUtils.resolveHost(host); // If we can create a connection then we have punched though the NAT, // authenticated and confirmed the nodeId matches const proxyAddress = networkUtils.buildAddress( diff --git a/src/nodes/NodeManager.ts b/src/nodes/NodeManager.ts index ffd9aa18f..bb264de4f 100644 --- a/src/nodes/NodeManager.ts +++ b/src/nodes/NodeManager.ts @@ -410,6 +410,12 @@ class NodeManager { timeout?: number, tran?: DBTransaction, ): Promise { + // We don't want to add our own node + if (nodeId.equals(this.keyManager.getNodeId())) { + this.logger.debug('Is own NodeId, skipping'); + return; + } + if (tran == null) { return this.db.withTransactionF(async (tran) => this.setNode(nodeId, nodeAddress, block, force, timeout, tran), diff --git a/src/nodes/errors.ts b/src/nodes/errors.ts index b35a58f70..bc0185025 100644 --- a/src/nodes/errors.ts +++ b/src/nodes/errors.ts @@ -86,6 +86,11 @@ class ErrorNodeConnectionHostWildcard extends ErrorNodes { static description = 'An IP wildcard was provided for the target host'; exitCode = sysexits.USAGE; } +class ErrorNodePingFailed extends ErrorNodes { + static description = + 'Failed to ping the node when attempting to authenticate'; + exitCode = sysexits.NOHOST; +} export { ErrorNodes, @@ -106,4 +111,5 @@ export { ErrorNodeConnectionPublicKeyNotFound, ErrorNodeConnectionManagerNotRunning, ErrorNodeConnectionHostWildcard, + ErrorNodePingFailed, }; diff --git a/src/proto/js/google/protobuf/any_pb.js b/src/proto/js/google/protobuf/any_pb.js index 2154f2078..cec1761c8 100644 --- a/src/proto/js/google/protobuf/any_pb.js +++ b/src/proto/js/google/protobuf/any_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/descriptor_pb.js b/src/proto/js/google/protobuf/descriptor_pb.js index 64e84878b..9c345b93d 100644 --- a/src/proto/js/google/protobuf/descriptor_pb.js +++ b/src/proto/js/google/protobuf/descriptor_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/duration_pb.js b/src/proto/js/google/protobuf/duration_pb.js index 74166f0fd..1b5f0fd84 100644 --- a/src/proto/js/google/protobuf/duration_pb.js +++ b/src/proto/js/google/protobuf/duration_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/empty_pb.js b/src/proto/js/google/protobuf/empty_pb.js index d85fa310a..bd5d8a4e1 100644 --- a/src/proto/js/google/protobuf/empty_pb.js +++ b/src/proto/js/google/protobuf/empty_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/field_mask_pb.js b/src/proto/js/google/protobuf/field_mask_pb.js index 67860a3a2..34e581b04 100644 --- a/src/proto/js/google/protobuf/field_mask_pb.js +++ b/src/proto/js/google/protobuf/field_mask_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/struct_pb.js b/src/proto/js/google/protobuf/struct_pb.js index bff1ed412..b16b8b2fa 100644 --- a/src/proto/js/google/protobuf/struct_pb.js +++ b/src/proto/js/google/protobuf/struct_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/timestamp_pb.js b/src/proto/js/google/protobuf/timestamp_pb.js index 6881a1d93..a270c1c47 100644 --- a/src/proto/js/google/protobuf/timestamp_pb.js +++ b/src/proto/js/google/protobuf/timestamp_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/google/protobuf/wrappers_pb.js b/src/proto/js/google/protobuf/wrappers_pb.js index 9c89af542..458e1b436 100644 --- a/src/proto/js/google/protobuf/wrappers_pb.js +++ b/src/proto/js/google/protobuf/wrappers_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/agent/agent_pb.js b/src/proto/js/polykey/v1/agent/agent_pb.js index 13a458c48..29361addf 100644 --- a/src/proto/js/polykey/v1/agent/agent_pb.js +++ b/src/proto/js/polykey/v1/agent/agent_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/agent_service_pb.js b/src/proto/js/polykey/v1/agent_service_pb.js index ade0b70fa..9fa48c738 100644 --- a/src/proto/js/polykey/v1/agent_service_pb.js +++ b/src/proto/js/polykey/v1/agent_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts index 067688187..b230f8df4 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts @@ -122,12 +122,12 @@ interface IClientServiceService_IAgentUnlock extends grpc.MethodDefinition; responseDeserialize: grpc.deserialize; } -interface IClientServiceService_INodesAdd extends grpc.MethodDefinition { +interface IClientServiceService_INodesAdd extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/NodesAdd"; requestStream: false; responseStream: false; - requestSerialize: grpc.serialize; - requestDeserialize: grpc.deserialize; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } @@ -679,7 +679,7 @@ export interface IClientServiceServer extends grpc.UntypedServiceImplementation agentStatus: grpc.handleUnaryCall; agentStop: grpc.handleUnaryCall; agentUnlock: grpc.handleUnaryCall; - nodesAdd: grpc.handleUnaryCall; + nodesAdd: grpc.handleUnaryCall; nodesPing: grpc.handleUnaryCall; nodesClaim: grpc.handleUnaryCall; nodesFind: grpc.handleUnaryCall; @@ -755,9 +755,9 @@ export interface IClientServiceClient { agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; @@ -943,9 +943,9 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public agentUnlock(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; - public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAddress, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public nodesAdd(request: polykey_v1_nodes_nodes_pb.NodeAdd, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; public nodesPing(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.StatusMessage) => void): grpc.ClientUnaryCall; diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.js b/src/proto/js/polykey/v1/client_service_grpc_pb.js index 642127423..e08b6512c 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.js +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.js @@ -201,6 +201,17 @@ function deserialize_polykey_v1_nodes_Node(buffer_arg) { return polykey_v1_nodes_nodes_pb.Node.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_polykey_v1_nodes_NodeAdd(arg) { + if (!(arg instanceof polykey_v1_nodes_nodes_pb.NodeAdd)) { + throw new Error('Expected argument of type polykey.v1.nodes.NodeAdd'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_polykey_v1_nodes_NodeAdd(buffer_arg) { + return polykey_v1_nodes_nodes_pb.NodeAdd.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_polykey_v1_nodes_NodeAddress(arg) { if (!(arg instanceof polykey_v1_nodes_nodes_pb.NodeAddress)) { throw new Error('Expected argument of type polykey.v1.nodes.NodeAddress'); @@ -528,10 +539,10 @@ nodesAdd: { path: '/polykey.v1.ClientService/NodesAdd', requestStream: false, responseStream: false, - requestType: polykey_v1_nodes_nodes_pb.NodeAddress, + requestType: polykey_v1_nodes_nodes_pb.NodeAdd, responseType: polykey_v1_utils_utils_pb.EmptyMessage, - requestSerialize: serialize_polykey_v1_nodes_NodeAddress, - requestDeserialize: deserialize_polykey_v1_nodes_NodeAddress, + requestSerialize: serialize_polykey_v1_nodes_NodeAdd, + requestDeserialize: deserialize_polykey_v1_nodes_NodeAdd, responseSerialize: serialize_polykey_v1_utils_EmptyMessage, responseDeserialize: deserialize_polykey_v1_utils_EmptyMessage, }, diff --git a/src/proto/js/polykey/v1/client_service_pb.js b/src/proto/js/polykey/v1/client_service_pb.js index 4adc8bd6d..68a9ebcb8 100644 --- a/src/proto/js/polykey/v1/client_service_pb.js +++ b/src/proto/js/polykey/v1/client_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/gestalts/gestalts_pb.js b/src/proto/js/polykey/v1/gestalts/gestalts_pb.js index 90435b3be..36b225293 100644 --- a/src/proto/js/polykey/v1/gestalts/gestalts_pb.js +++ b/src/proto/js/polykey/v1/gestalts/gestalts_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/identities/identities_pb.js b/src/proto/js/polykey/v1/identities/identities_pb.js index cbfb21ed9..a52a535f4 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.js +++ b/src/proto/js/polykey/v1/identities/identities_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/keys/keys_pb.js b/src/proto/js/polykey/v1/keys/keys_pb.js index dfbc1df0b..323ef8f16 100644 --- a/src/proto/js/polykey/v1/keys/keys_pb.js +++ b/src/proto/js/polykey/v1/keys/keys_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts b/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts index 79d0fbd58..09fb028eb 100644 --- a/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts +++ b/src/proto/js/polykey/v1/nodes/nodes_pb.d.ts @@ -98,6 +98,38 @@ export namespace Claim { } } +export class NodeAdd extends jspb.Message { + getNodeId(): string; + setNodeId(value: string): NodeAdd; + + hasAddress(): boolean; + clearAddress(): void; + getAddress(): Address | undefined; + setAddress(value?: Address): NodeAdd; + getForce(): boolean; + setForce(value: boolean): NodeAdd; + getPing(): boolean; + setPing(value: boolean): NodeAdd; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): NodeAdd.AsObject; + static toObject(includeInstance: boolean, msg: NodeAdd): NodeAdd.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: NodeAdd, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): NodeAdd; + static deserializeBinaryFromReader(message: NodeAdd, reader: jspb.BinaryReader): NodeAdd; +} + +export namespace NodeAdd { + export type AsObject = { + nodeId: string, + address?: Address.AsObject, + force: boolean, + ping: boolean, + } +} + export class NodeBuckets extends jspb.Message { getBucketsMap(): jspb.Map; diff --git a/src/proto/js/polykey/v1/nodes/nodes_pb.js b/src/proto/js/polykey/v1/nodes/nodes_pb.js index 8fe0c189f..6dd70cdc3 100644 --- a/src/proto/js/polykey/v1/nodes/nodes_pb.js +++ b/src/proto/js/polykey/v1/nodes/nodes_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public @@ -24,6 +25,7 @@ goog.exportSymbol('proto.polykey.v1.nodes.Claims', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Connection', null, global); goog.exportSymbol('proto.polykey.v1.nodes.CrossSign', null, global); goog.exportSymbol('proto.polykey.v1.nodes.Node', null, global); +goog.exportSymbol('proto.polykey.v1.nodes.NodeAdd', null, global); goog.exportSymbol('proto.polykey.v1.nodes.NodeAddress', null, global); goog.exportSymbol('proto.polykey.v1.nodes.NodeBuckets', null, global); goog.exportSymbol('proto.polykey.v1.nodes.NodeTable', null, global); @@ -113,6 +115,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.polykey.v1.nodes.Claim.displayName = 'proto.polykey.v1.nodes.Claim'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.polykey.v1.nodes.NodeAdd = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.nodes.NodeAdd, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.nodes.NodeAdd.displayName = 'proto.polykey.v1.nodes.NodeAdd'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -978,6 +1001,247 @@ proto.polykey.v1.nodes.Claim.prototype.setForceInvite = function(value) { +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.nodes.NodeAdd.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.polykey.v1.nodes.NodeAdd} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeAdd.toObject = function(includeInstance, msg) { + var f, obj = { + nodeId: jspb.Message.getFieldWithDefault(msg, 1, ""), + address: (f = msg.getAddress()) && proto.polykey.v1.nodes.Address.toObject(includeInstance, f), + force: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), + ping: jspb.Message.getBooleanFieldWithDefault(msg, 4, false) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.polykey.v1.nodes.NodeAdd} + */ +proto.polykey.v1.nodes.NodeAdd.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.nodes.NodeAdd; + return proto.polykey.v1.nodes.NodeAdd.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.nodes.NodeAdd} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.nodes.NodeAdd} + */ +proto.polykey.v1.nodes.NodeAdd.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setNodeId(value); + break; + case 2: + var value = new proto.polykey.v1.nodes.Address; + reader.readMessage(value,proto.polykey.v1.nodes.Address.deserializeBinaryFromReader); + msg.setAddress(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setForce(value); + break; + case 4: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setPing(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.nodes.NodeAdd.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.polykey.v1.nodes.NodeAdd} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.nodes.NodeAdd.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getNodeId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getAddress(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.polykey.v1.nodes.Address.serializeBinaryToWriter + ); + } + f = message.getForce(); + if (f) { + writer.writeBool( + 3, + f + ); + } + f = message.getPing(); + if (f) { + writer.writeBool( + 4, + f + ); + } +}; + + +/** + * optional string node_id = 1; + * @return {string} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getNodeId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setNodeId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional Address address = 2; + * @return {?proto.polykey.v1.nodes.Address} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getAddress = function() { + return /** @type{?proto.polykey.v1.nodes.Address} */ ( + jspb.Message.getWrapperField(this, proto.polykey.v1.nodes.Address, 2)); +}; + + +/** + * @param {?proto.polykey.v1.nodes.Address|undefined} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this +*/ +proto.polykey.v1.nodes.NodeAdd.prototype.setAddress = function(value) { + return jspb.Message.setWrapperField(this, 2, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.clearAddress = function() { + return this.setAddress(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.hasAddress = function() { + return jspb.Message.getField(this, 2) != null; +}; + + +/** + * optional bool force = 3; + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getForce = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setForce = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + +/** + * optional bool ping = 4; + * @return {boolean} + */ +proto.polykey.v1.nodes.NodeAdd.prototype.getPing = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.polykey.v1.nodes.NodeAdd} returns this + */ +proto.polykey.v1.nodes.NodeAdd.prototype.setPing = function(value) { + return jspb.Message.setProto3BooleanField(this, 4, value); +}; + + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. diff --git a/src/proto/js/polykey/v1/notifications/notifications_pb.js b/src/proto/js/polykey/v1/notifications/notifications_pb.js index f50f614f5..80794ae7f 100644 --- a/src/proto/js/polykey/v1/notifications/notifications_pb.js +++ b/src/proto/js/polykey/v1/notifications/notifications_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/permissions/permissions_pb.js b/src/proto/js/polykey/v1/permissions/permissions_pb.js index 53e129985..1b55e4f47 100644 --- a/src/proto/js/polykey/v1/permissions/permissions_pb.js +++ b/src/proto/js/polykey/v1/permissions/permissions_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/secrets/secrets_pb.js b/src/proto/js/polykey/v1/secrets/secrets_pb.js index 5008028d8..28d2e02ae 100644 --- a/src/proto/js/polykey/v1/secrets/secrets_pb.js +++ b/src/proto/js/polykey/v1/secrets/secrets_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/sessions/sessions_pb.js b/src/proto/js/polykey/v1/sessions/sessions_pb.js index c2d81541f..212d584bc 100644 --- a/src/proto/js/polykey/v1/sessions/sessions_pb.js +++ b/src/proto/js/polykey/v1/sessions/sessions_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/test_service_pb.js b/src/proto/js/polykey/v1/test_service_pb.js index f5ab8f2de..56dd0245c 100644 --- a/src/proto/js/polykey/v1/test_service_pb.js +++ b/src/proto/js/polykey/v1/test_service_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/utils/utils_pb.js b/src/proto/js/polykey/v1/utils/utils_pb.js index 852c0903d..39b5c869e 100644 --- a/src/proto/js/polykey/v1/utils/utils_pb.js +++ b/src/proto/js/polykey/v1/utils/utils_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/js/polykey/v1/vaults/vaults_pb.js b/src/proto/js/polykey/v1/vaults/vaults_pb.js index 6b793dc63..153565a46 100644 --- a/src/proto/js/polykey/v1/vaults/vaults_pb.js +++ b/src/proto/js/polykey/v1/vaults/vaults_pb.js @@ -2,6 +2,7 @@ /** * @fileoverview * @enhanceable + * @suppress {missingRequire} reports error on implicit type usages. * @suppress {messageConventions} JS Compiler reports an error if a variable or * field starts with 'MSG_' and isn't a translatable message. * @public diff --git a/src/proto/schemas/polykey/v1/client_service.proto b/src/proto/schemas/polykey/v1/client_service.proto index 81782f13b..9c90e0286 100644 --- a/src/proto/schemas/polykey/v1/client_service.proto +++ b/src/proto/schemas/polykey/v1/client_service.proto @@ -22,7 +22,7 @@ service ClientService { rpc AgentUnlock (polykey.v1.utils.EmptyMessage) returns (polykey.v1.utils.EmptyMessage); // Nodes - rpc NodesAdd(polykey.v1.nodes.NodeAddress) returns (polykey.v1.utils.EmptyMessage); + rpc NodesAdd(polykey.v1.nodes.NodeAdd) returns (polykey.v1.utils.EmptyMessage); rpc NodesPing(polykey.v1.nodes.Node) returns (polykey.v1.utils.StatusMessage); rpc NodesClaim(polykey.v1.nodes.Claim) returns (polykey.v1.utils.StatusMessage); rpc NodesFind(polykey.v1.nodes.Node) returns (polykey.v1.nodes.NodeAddress); diff --git a/src/proto/schemas/polykey/v1/nodes/nodes.proto b/src/proto/schemas/polykey/v1/nodes/nodes.proto index bd2b54f85..cd8b23785 100644 --- a/src/proto/schemas/polykey/v1/nodes/nodes.proto +++ b/src/proto/schemas/polykey/v1/nodes/nodes.proto @@ -25,6 +25,13 @@ message Claim { bool force_invite = 2; } +message NodeAdd { + string node_id = 1; + Address address = 2; + bool force = 3; + bool ping = 4; +} + // Bucket index -> a node bucket (from NodeGraph) message NodeBuckets { map buckets = 1; diff --git a/tests/bin/nodes/add.test.ts b/tests/bin/nodes/add.test.ts index 85b598786..b3bd7cc67 100644 --- a/tests/bin/nodes/add.test.ts +++ b/tests/bin/nodes/add.test.ts @@ -9,6 +9,7 @@ import { sysexits } from '@/utils'; import PolykeyAgent from '@/PolykeyAgent'; import * as nodesUtils from '@/nodes/utils'; import * as keysUtils from '@/keys/utils'; +import NodeManager from '@/nodes/NodeManager'; import * as testBinUtils from '../utils'; import * as testUtils from '../../utils'; import * as testNodesUtils from '../../nodes/utils'; @@ -20,12 +21,13 @@ describe('add', () => { const invalidNodeId = IdInternal.fromString('INVALIDID'); const validHost = '0.0.0.0'; const invalidHost = 'INVALIDHOST'; - const port = '55555'; + const port = 55555; let dataDir: string; let nodePath: string; let pkAgent: PolykeyAgent; let mockedGenerateKeyPair: jest.SpyInstance; let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + let mockedPingNode: jest.SpyInstance; beforeAll(async () => { const globalKeyPair = await testUtils.setupGlobalKeypair(); mockedGenerateKeyPair = jest @@ -38,6 +40,7 @@ describe('add', () => { path.join(os.tmpdir(), 'polykey-test-'), ); nodePath = path.join(dataDir, 'polykey'); + mockedPingNode = jest.spyOn(NodeManager.prototype, 'pingNode'); // Cannot use the shared global agent since we can't 'un-add' a node pkAgent = await PolykeyAgent.createPolykeyAgent({ password, @@ -60,10 +63,22 @@ describe('add', () => { }); mockedGenerateKeyPair.mockRestore(); mockedGenerateDeterministicKeyPair.mockRestore(); + mockedPingNode.mockRestore(); + }); + beforeEach(async () => { + await pkAgent.nodeGraph.stop(); + await pkAgent.nodeGraph.start({ fresh: true }); + mockedPingNode.mockImplementation(() => true); }); test('adds a node', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), validHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -81,11 +96,17 @@ describe('add', () => { dataDir, ); expect(stdout).toContain(validHost); - expect(stdout).toContain(port); + expect(stdout).toContain(`${port}`); }); test('fails to add a node (invalid node ID)', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(invalidNodeId), validHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(invalidNodeId), + validHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -96,7 +117,13 @@ describe('add', () => { }); test('fails to add a node (invalid IP address)', async () => { const { exitCode } = await testBinUtils.pkStdio( - ['nodes', 'add', nodesUtils.encodeNodeId(validNodeId), invalidHost, port], + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + invalidHost, + `${port}`, + ], { PK_NODE_PATH: nodePath, PK_PASSWORD: password, @@ -105,4 +132,65 @@ describe('add', () => { ); expect(exitCode).toBe(sysexits.USAGE); }); + test('adds a node with --force flag', async () => { + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + '--force', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + // Checking if node was added. + const node = await pkAgent.nodeGraph.getNode(validNodeId); + expect(node?.address).toEqual({ host: validHost, port: port }); + }); + test('fails to add node when ping fails', async () => { + mockedPingNode.mockImplementation(() => false); + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.NOHOST); + }); + test('adds a node with --no-ping flag', async () => { + mockedPingNode.mockImplementation(() => false); + const { exitCode } = await testBinUtils.pkStdio( + [ + 'nodes', + 'add', + '--no-ping', + nodesUtils.encodeNodeId(validNodeId), + validHost, + `${port}`, + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(0); + // Checking if node was added. + const node = await pkAgent.nodeGraph.getNode(validNodeId); + expect(node?.address).toEqual({ host: validHost, port: port }); + }); }); diff --git a/tests/bin/nodes/find.test.ts b/tests/bin/nodes/find.test.ts index 56bffd263..b60804c64 100644 --- a/tests/bin/nodes/find.test.ts +++ b/tests/bin/nodes/find.test.ts @@ -158,31 +158,37 @@ describe('find', () => { port: remoteOfflinePort, }); }); - test('fails to find an unknown node', async () => { - const unknownNodeId = nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - ); - const { exitCode, stdout } = await testBinUtils.pkStdio( - [ - 'nodes', - 'find', - nodesUtils.encodeNodeId(unknownNodeId!), - '--format', - 'json', - ], - { - PK_NODE_PATH: nodePath, - PK_PASSWORD: password, - }, - dataDir, - ); - expect(exitCode).toBe(sysexits.GENERAL); - expect(JSON.parse(stdout)).toEqual({ - success: false, - message: `Failed to find node ${nodesUtils.encodeNodeId(unknownNodeId!)}`, - id: nodesUtils.encodeNodeId(unknownNodeId!), - host: '', - port: 0, - }); - }); + test( + 'fails to find an unknown node', + async () => { + const unknownNodeId = nodesUtils.decodeNodeId( + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', + ); + const { exitCode, stdout } = await testBinUtils.pkStdio( + [ + 'nodes', + 'find', + nodesUtils.encodeNodeId(unknownNodeId!), + '--format', + 'json', + ], + { + PK_NODE_PATH: nodePath, + PK_PASSWORD: password, + }, + dataDir, + ); + expect(exitCode).toBe(sysexits.GENERAL); + expect(JSON.parse(stdout)).toEqual({ + success: false, + message: `Failed to find node ${nodesUtils.encodeNodeId( + unknownNodeId!, + )}`, + id: nodesUtils.encodeNodeId(unknownNodeId!), + host: '', + port: 0, + }); + }, + global.failedConnectionTimeout, + ); }); diff --git a/tests/client/service/nodesAdd.test.ts b/tests/client/service/nodesAdd.test.ts index f2c4969a0..f00e62566 100644 --- a/tests/client/service/nodesAdd.test.ts +++ b/tests/client/service/nodesAdd.test.ts @@ -163,13 +163,15 @@ describe('nodesAdd', () => { const addressMessage = new nodesPB.Address(); addressMessage.setHost('127.0.0.1'); addressMessage.setPort(11111); - const request = new nodesPB.NodeAddress(); + const request = new nodesPB.NodeAdd(); request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); request.setAddress(addressMessage); const response = await grpcClient.nodesAdd( request, clientUtils.encodeAuthFromPassword(password), ); + request.setPing(false); + request.setForce(false); expect(response).toBeInstanceOf(utilsPB.EmptyMessage); const result = await nodeGraph.getNode( nodesUtils.decodeNodeId( @@ -184,7 +186,9 @@ describe('nodesAdd', () => { const addressMessage = new nodesPB.Address(); addressMessage.setHost(''); addressMessage.setPort(11111); - const request = new nodesPB.NodeAddress(); + const request = new nodesPB.NodeAdd(); + request.setPing(false); + request.setForce(false); request.setNodeId('vrsc24a1er424epq77dtoveo93meij0pc8ig4uvs9jbeld78n9nl0'); request.setAddress(addressMessage); await expectRemoteError( diff --git a/tests/nodes/NodeConnectionManager.general.test.ts b/tests/nodes/NodeConnectionManager.general.test.ts index f0fe65d4e..17035b4dd 100644 --- a/tests/nodes/NodeConnectionManager.general.test.ts +++ b/tests/nodes/NodeConnectionManager.general.test.ts @@ -141,7 +141,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { password, nodePath: path.join(dataDir2, 'remoteNode1'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: logger.getChild('remoteNode1'), }); @@ -150,7 +153,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { password, nodePath: path.join(dataDir2, 'remoteNode2'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: logger.getChild('remoteNode2'), }); @@ -246,7 +252,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { // Case 1: node already exists in the local node graph (no contact required) const nodeId = nodeId1; const nodeAddress: NodeAddress = { - host: '127.0.0.1' as Host, + host: localHost, port: 11111 as Port, }; await nodeGraph.setNode(nodeId, nodeAddress); @@ -261,6 +267,11 @@ describe(`${NodeConnectionManager.name} general test`, () => { test( 'finds node (contacts remote node)', async () => { + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); // NodeConnectionManager under test const nodeConnectionManager = new NodeConnectionManager({ keyManager, @@ -274,14 +285,17 @@ describe(`${NodeConnectionManager.name} general test`, () => { // Case 2: node can be found on the remote node const nodeId = nodeId1; const nodeAddress: NodeAddress = { - host: '127.0.0.1' as Host, + host: localHost, port: 11111 as Port, }; const server = await PolykeyAgent.createPolykeyAgent({ nodePath: path.join(dataDir, 'node2'), password, networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: nodeConnectionManagerLogger, }); @@ -296,6 +310,7 @@ describe(`${NodeConnectionManager.name} general test`, () => { await server.stop(); } finally { await nodeConnectionManager.stop(); + mockedPingNode.mockRestore(); } }, global.polykeyStartupTimeout, @@ -319,7 +334,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { nodePath: path.join(dataDir, 'node3'), password, networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, logger: nodeConnectionManagerLogger, }); @@ -355,7 +373,10 @@ describe(`${NodeConnectionManager.name} general test`, () => { logger: logger.getChild('serverPKAgent'), nodePath: path.join(dataDir, 'serverPKAgent'), networkConfig: { - proxyHost: '127.0.0.1' as Host, + proxyHost: localHost, + agentHost: localHost, + clientHost: localHost, + forwardHost: localHost, }, }); nodeConnectionManager = new NodeConnectionManager({ diff --git a/tests/nodes/NodeConnectionManager.seednodes.test.ts b/tests/nodes/NodeConnectionManager.seednodes.test.ts index e6d91f399..63ba90e9d 100644 --- a/tests/nodes/NodeConnectionManager.seednodes.test.ts +++ b/tests/nodes/NodeConnectionManager.seednodes.test.ts @@ -193,6 +193,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { // Seed nodes test('starting should add seed nodes to the node graph', async () => { let nodeConnectionManager: NodeConnectionManager | undefined; + let nodeManager: NodeManager | undefined; try { nodeConnectionManager = new NodeConnectionManager({ keyManager, @@ -204,7 +205,17 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { seedNodes: dummySeedNodes, logger: logger, }); - await nodeConnectionManager.start({ nodeManager: dummyNodeManager }); + nodeManager = new NodeManager({ + db, + keyManager, + logger, + nodeConnectionManager, + nodeGraph, + queue: {} as Queue, + sigchain: {} as Sigchain, + }); + await nodeManager.start(); + await nodeConnectionManager.start({ nodeManager }); const seedNodes = nodeConnectionManager.getSeedNodes(); expect(seedNodes).toContainEqual(nodeId1); expect(seedNodes).toContainEqual(nodeId2); @@ -216,6 +227,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { } finally { // Clean up await nodeConnectionManager?.stop(); + await nodeManager?.stop(); } }); test('should get seed nodes', async () => { @@ -250,6 +262,11 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { 'refreshBucket', ); mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { const seedNodes: SeedNodes = {}; seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { @@ -295,6 +312,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { expect(await nodeGraph.getNode(dummyNodeId)).toBeUndefined(); } finally { mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); await nodeManager?.stop(); await nodeConnectionManager?.stop(); await queue?.stop(); @@ -309,6 +327,11 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { 'refreshBucket', ); mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { const seedNodes: SeedNodes = {}; seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { @@ -353,6 +376,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { expect(mockedRefreshBucket).toHaveBeenCalled(); } finally { mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); await nodeManager?.stop(); await nodeConnectionManager?.stop(); await queue?.stop(); @@ -367,6 +391,11 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { 'refreshBucket', ); mockedRefreshBucket.mockImplementation(async () => {}); + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { const seedNodes: SeedNodes = {}; seedNodes[nodesUtils.encodeNodeId(remoteNodeId1)] = { @@ -419,6 +448,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { expect(await nodeGraph.getNode(nodeId2)).toBeDefined(); } finally { mockedRefreshBucket.mockRestore(); + mockedPingNode.mockRestore(); await nodeConnectionManager?.stop(); await nodeManager?.stop(); await queue?.stop(); @@ -440,6 +470,11 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { host: remoteNode2.proxy.getProxyHost(), port: remoteNode2.proxy.getProxyPort(), }; + const mockedPingNode = jest.spyOn( + NodeConnectionManager.prototype, + 'pingNode', + ); + mockedPingNode.mockImplementation(async () => true); try { logger.setLevel(LogLevel.WARN); node1 = await PolykeyAgent.createPolykeyAgent({ @@ -499,6 +534,7 @@ describe(`${NodeConnectionManager.name} seed nodes test`, () => { expect(node2Nodes).toContain(nodeIdR2); expect(node2Nodes).toContain(nodeId1); } finally { + mockedPingNode.mockRestore(); logger.setLevel(LogLevel.WARN); await node1?.stop(); await node1?.destroy();