Skip to content

Commit 1760ab5

Browse files
committed
feat: added tests for startup network join behaviour
1 parent 6d8c2cd commit 1760ab5

File tree

5 files changed

+199
-11
lines changed

5 files changed

+199
-11
lines changed

jest.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import path from 'node:path';
22
import url from 'node:url';
3-
import tsconfigJSON from './tsconfig.json' assert { type: "json" };
3+
import tsconfigJSON from './tsconfig.json' with { type: "json" };
44

55
const projectPath = path.dirname(url.fileURLToPath(import.meta.url));
66

src/nodes/NodeManager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ class NodeManager<Manifest extends AgentClientManifestNodeManager> {
301301
if (ctx.signal.aborted) return;
302302

303303
if (network != null) {
304-
try {
304+
if (this.getClaimNetworkAccess(network) == null) {
305305
await this.claimNetwork(successfulConnections[0].value.nodeId, network);
306-
} catch {
306+
} else {
307307
await this.switchNetwork(network);
308308
}
309309
}
@@ -1757,7 +1757,7 @@ class NodeManager<Manifest extends AgentClientManifestNodeManager> {
17571757
/**
17581758
* This returns the `ClaimNetworkAccess` for the given network.
17591759
*/
1760-
protected async getClaimNetworkAccess(
1760+
public async getClaimNetworkAccess(
17611761
network: string,
17621762
tran?: DBTransaction,
17631763
): Promise<Token<ClaimNetworkAccess> | undefined> {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ContextTimed } from '@matrixai/contexts';
2+
import type { JSONValue } from '@matrixai/rpc';
3+
import type NodeManager from '../../NodeManager.js';
4+
import type {
5+
AgentRPCRequestParams,
6+
AgentRPCResponseResult,
7+
NodesSyncGraphMessage,
8+
} from '../types.js';
9+
import type { AgentClientManifest } from '../callers/index.js';
10+
import { UnaryHandler } from '@matrixai/rpc';
11+
12+
class NodesSyncGraph extends UnaryHandler<
13+
{
14+
nodeManager: NodeManager<AgentClientManifest>;
15+
},
16+
AgentRPCRequestParams<NodesSyncGraphMessage>,
17+
AgentRPCResponseResult
18+
> {
19+
public handle = async (
20+
input: AgentRPCRequestParams<NodesSyncGraphMessage>,
21+
_cancel: (reason?: any) => void,
22+
_meta: Record<string, JSONValue> | undefined,
23+
ctx: ContextTimed,
24+
): Promise<AgentRPCResponseResult> => {
25+
const {
26+
nodeManager,
27+
}: {
28+
nodeManager: NodeManager<AgentClientManifest>;
29+
} = this.container;
30+
await nodeManager.syncNodeGraph(
31+
input.network,
32+
input.initialNodes,
33+
input.connectionTimeout,
34+
true,
35+
ctx,
36+
);
37+
return {};
38+
};
39+
}
40+
41+
export default NodesSyncGraph;

src/nodes/agent/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import type {
1313
import type { VaultAction, VaultName } from '../../vaults/types.js';
1414
import type { SignedNotification } from '../../notifications/types.js';
1515
import type { Host, Hostname, Port } from '../../network/types.js';
16-
import type { NetworkId, NodeContact } from '../../nodes/types.js';
16+
import type {
17+
NetworkId,
18+
NodeContact,
19+
NodeId,
20+
NodeAddress,
21+
} from '../../nodes/types.js';
1722
import type { AuditEvent } from '../../audit/types.js';
1823
import type { SignedClaimEncoded } from '../../claims/types.js';
1924

@@ -125,6 +130,12 @@ type NodesAuthenticateConnectionMessageNone = {
125130
type: 'NodesAuthenticateConnectionMessageNone';
126131
};
127132

133+
type NodesSyncGraphMessage = {
134+
network: string;
135+
initialNodes: Array<[NodeId, NodeAddress]>;
136+
connectionTimeout: number;
137+
};
138+
128139
export type {
129140
AgentRPCRequestParams,
130141
AgentRPCResponseResult,
@@ -147,4 +158,5 @@ export type {
147158
NodesAuthenticateConnectionMessagePrivate,
148159
NodesAuthenticateConnectionMessageBasicPublic,
149160
NodesAuthenticateConnectionMessageNone,
161+
NodesSyncGraphMessage,
150162
};

tests/nodes/NodeManager.test.ts

Lines changed: 141 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2967,11 +2967,146 @@ describe(`${NodeManager.name}`, () => {
29672967
node1.nodeManager.claimNetwork(seedNodeId, network),
29682968
).rejects.toThrow(claimsErrors.ErrorEmptyStream);
29692969
});
2970-
// verify the claim exists
2971-
test.todo(
2972-
'node should automatically request NetworkAccessClaim if it does not exist',
2973-
);
2974-
// verify the claim is unmodified
2975-
test.todo('node should not request new NetworkAccessClaim if it exist');
2970+
test('node should automatically request a claim if it does not exist', async () => {
2971+
// Creating network credentials
2972+
const networkKeyPair = keysUtils.generateKeyPair();
2973+
const networkNodeId = keysUtils.publicKeyToNodeId(
2974+
networkKeyPair.publicKey,
2975+
);
2976+
const network = 'public.network.com';
2977+
2978+
// Setting up seed nodes claims
2979+
const seedNode = await createPeerNode();
2980+
const [, seedNodeClaimNetworkAuthority] =
2981+
await seedNode.nodeManager.createClaimNetworkAuthority(
2982+
networkNodeId,
2983+
network,
2984+
false,
2985+
async (claim) => {
2986+
claim.signWithPrivateKey(networkKeyPair.privateKey);
2987+
return claim;
2988+
},
2989+
);
2990+
await seedNode.nodeManager.createSelfSignedClaimNetworkAccess(
2991+
seedNodeClaimNetworkAuthority,
2992+
);
2993+
const seedNodeId = seedNode.keyRing.getNodeId();
2994+
2995+
// Setting up the new node entering the network
2996+
const node1 = await createPeerNode();
2997+
// We intentionally do not claim the network manually
2998+
const node1Id = node1.keyRing.getNodeId();
2999+
await allowNodeToJoin(seedNode.gestaltGraph, node1Id);
3000+
// Connect to the seed node
3001+
await node1.nodeConnectionManager.createConnection(
3002+
[seedNodeId],
3003+
localHost,
3004+
seedNode.nodeConnectionManager.port,
3005+
);
3006+
3007+
await node1.nodeManager.syncNodeGraph(
3008+
network,
3009+
[
3010+
[
3011+
seedNode.keyRing.getNodeId(),
3012+
[localHost, seedNode.nodeConnectionManager.port],
3013+
],
3014+
],
3015+
1000,
3016+
true,
3017+
);
3018+
3019+
// We have now proved that a node can request access to the network from a node with network authority.
3020+
// Now We should be able to connect while authenticated to the seed node.
3021+
3022+
// Re-initiate authentication
3023+
await seedNode.nodeConnectionManager.destroyConnection(node1Id, true);
3024+
await node1.nodeConnectionManager.destroyConnection(seedNodeId, true);
3025+
await node1.nodeConnectionManager.createConnection(
3026+
[seedNodeId],
3027+
localHost,
3028+
seedNode.nodeConnectionManager.port,
3029+
);
3030+
3031+
const networkAccess =
3032+
await node1.nodeManager.getClaimNetworkAccess(network);
3033+
if (networkAccess == null) {
3034+
throw new Error('network access claim not found');
3035+
}
3036+
claimNetworkAccessUtils.verifyClaimNetworkAccess(
3037+
networkNodeId,
3038+
node1Id,
3039+
network,
3040+
networkAccess,
3041+
);
3042+
3043+
await node1.nodeManager.withConnF(seedNodeId, undefined, async () => {
3044+
// Do nothing
3045+
});
3046+
});
3047+
test('node should not request new claim if it already exists', async () => {
3048+
// Creating network credentials
3049+
const networkKeyPair = keysUtils.generateKeyPair();
3050+
const networkNodeId = keysUtils.publicKeyToNodeId(
3051+
networkKeyPair.publicKey,
3052+
);
3053+
const network = 'test.network.com';
3054+
3055+
// Setting up seed nodes claims
3056+
const seedNode = await createPeerNode();
3057+
const [, seedNodeClaimNetworkAuthority] =
3058+
await seedNode.nodeManager.createClaimNetworkAuthority(
3059+
networkNodeId,
3060+
network,
3061+
true,
3062+
async (claim) => {
3063+
claim.signWithPrivateKey(networkKeyPair.privateKey);
3064+
return claim;
3065+
},
3066+
);
3067+
await seedNode.nodeManager.createSelfSignedClaimNetworkAccess(
3068+
seedNodeClaimNetworkAuthority,
3069+
);
3070+
const seedNodeId = seedNode.keyRing.getNodeId();
3071+
3072+
// Setting up the new node entering the network
3073+
const node1 = await createPeerNode();
3074+
3075+
const node1Id = node1.keyRing.getNodeId();
3076+
await allowNodeToJoin(seedNode.gestaltGraph, node1Id);
3077+
3078+
// Connect to the seednode
3079+
await node1.nodeConnectionManager.createConnection(
3080+
[seedNodeId],
3081+
localHost,
3082+
seedNode.nodeConnectionManager.port,
3083+
);
3084+
3085+
// Create a network access claim
3086+
await node1.nodeManager.claimNetwork(seedNodeId, network);
3087+
3088+
// Re-initiate authentication
3089+
await seedNode.nodeConnectionManager.destroyConnection(node1Id, true);
3090+
await node1.nodeConnectionManager.destroyConnection(seedNodeId, true);
3091+
await node1.nodeConnectionManager.createConnection(
3092+
[seedNodeId],
3093+
localHost,
3094+
seedNode.nodeConnectionManager.port,
3095+
);
3096+
3097+
// Check the claim once we have re-authenticated
3098+
const token1 = await node1.nodeManager.getClaimNetworkAccess(network);
3099+
if (token1 == null) throw new Error('network access claim not found');
3100+
const token1Id = token1.payload.jti;
3101+
3102+
// Try claiming again
3103+
await expect(node1.nodeManager.claimNetwork(seedNodeId, network)).toReject();
3104+
3105+
// The token should not have changed
3106+
const token2 = await node1.nodeManager.getClaimNetworkAccess(network);
3107+
if (token2 == null) throw new Error('network access claim not found');
3108+
const token2Id = token2.payload.jti;
3109+
expect(token1Id).toBe(token2Id);
3110+
});
29763111
});
29773112
});

0 commit comments

Comments
 (0)