Skip to content

Commit 1094c0d

Browse files
committed
feat: added cancellation and tests for some git operations
1 parent bd8902a commit 1094c0d

File tree

8 files changed

+274
-170
lines changed

8 files changed

+274
-170
lines changed

src/client/handlers/VaultsSecretsRemove.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class VaultsSecretsRemove extends DuplexHandler<
6767
// release it when cleaning up.
6868
const acquire = await vaultManager.withVaults(
6969
[vaultId],
70-
async (vault) => vault.acquireWrite(ctx),
70+
async (vault) => vault.acquireWrite(undefined, ctx),
7171
);
7272
vaultAcquires.push(acquire);
7373
}

src/git/http.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -365,9 +365,19 @@ async function* generatePackRequest(
365365
ctx: ContextTimed,
366366
): AsyncGenerator<Buffer, void, void> {
367367
const [wants, haves, _capabilities] = await parsePackRequest(body);
368+
const efsProxy = new Proxy(efs, {
369+
get: (target, key) => {
370+
ctx.signal.throwIfAborted();
371+
const resource = target[key];
372+
if (typeof resource === 'function') {
373+
return (...args) => resource.apply(target, args);
374+
}
375+
return resource;
376+
},
377+
});
368378
const objectIds = await gitUtils.listObjects(
369379
{
370-
efs: efs,
380+
efs: efsProxy,
371381
dir: dir,
372382
gitDir: gitDir,
373383
wants: wants,
@@ -377,7 +387,8 @@ async function* generatePackRequest(
377387
);
378388
// Reply that we have no common history and that we need to send everything
379389
yield packetLineBuffer(gitUtils.NAK_BUFFER);
380-
// Send everything over in pack format
390+
// Send everything over in pack format. This method already proxies efs, so
391+
// there's no need to double-proxy it.
381392
yield* generatePackData(
382393
{
383394
efs: efs,
@@ -421,15 +432,26 @@ async function* generatePackData(
421432
let packFile: PackObjectsResult;
422433
// In case of errors we don't want to throw them. This will result in the error being thrown into `isometric-git`
423434
// when it consumes the response. It handles this by logging out the error which we don't want to happen.
435+
const efsProxy = new Proxy(efs, {
436+
get: (target, key) => {
437+
ctx.signal.throwIfAborted();
438+
const resource = target[key];
439+
if (typeof resource === 'function') {
440+
return (...args) => resource.apply(target, args);
441+
}
442+
return resource;
443+
},
444+
});
424445
try {
425446
packFile = await git.packObjects({
426-
fs: efs,
447+
fs: efsProxy,
427448
dir: dir,
428449
gitdir: gitDir,
429450
oids: objectIds,
430451
});
431-
} catch {
452+
} catch (e) {
432453
// Return without sending any data
454+
if (e === ctx.signal.reason) throw e;
433455
return;
434456
}
435457
// Pack file will only be undefined if it was written to disk instead

src/nodes/NodeManager.ts

Lines changed: 114 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -499,17 +499,17 @@ class NodeManager {
499499
* @param f Function to handle communication
500500
* @param ctx
501501
*/
502-
public withConnF<T>(
502+
public async withConnF<T>(
503503
nodeId: NodeId,
504+
ctx: Partial<ContextTimedInput> | undefined,
504505
f: (conn: NodeConnection) => Promise<T>,
505-
ctx?: Partial<ContextTimedInput>,
506-
): PromiseCancellable<T>;
506+
): Promise<T>;
507507
@ready(new nodesErrors.ErrorNodeManagerNotRunning())
508508
public async withConnF<T>(
509509
nodeId: NodeId,
510-
f: (conn: NodeConnection) => Promise<T>,
511510
@context ctx: ContextTimed,
512-
) {
511+
f: (conn: NodeConnection) => Promise<T>,
512+
): Promise<T> {
513513
return await withF(
514514
[this.acquireConnection(nodeId, ctx)],
515515
async ([conn]) => {
@@ -1220,52 +1220,45 @@ class NodeManager {
12201220
@context ctx: ContextTimed,
12211221
): Promise<Record<ClaimId, SignedClaim>> {
12221222
// Verify the node's chain with its own public key
1223-
return await this.withConnF(
1224-
targetNodeId,
1225-
async (connection) => {
1226-
const claims: Record<ClaimId, SignedClaim> = {};
1227-
const client = connection.getClient();
1228-
for await (const agentClaim of await client.methods.nodesClaimsGet({
1229-
claimIdEncoded:
1230-
claimId != null
1231-
? claimsUtils.encodeClaimId(claimId)
1232-
: ('' as ClaimIdEncoded),
1233-
})) {
1234-
if (ctx.signal.aborted) throw ctx.signal.reason;
1235-
// Need to re-construct each claim
1236-
const claimId: ClaimId = claimsUtils.decodeClaimId(
1237-
agentClaim.claimIdEncoded,
1238-
)!;
1239-
const signedClaimEncoded = agentClaim.signedTokenEncoded;
1240-
const signedClaim = claimsUtils.parseSignedClaim(signedClaimEncoded);
1241-
// Verifying the claim
1242-
const issPublicKey = keysUtils.publicKeyFromNodeId(
1243-
nodesUtils.decodeNodeId(signedClaim.payload.iss)!,
1244-
);
1245-
const subPublicKey =
1246-
signedClaim.payload.typ === 'node'
1247-
? keysUtils.publicKeyFromNodeId(
1248-
nodesUtils.decodeNodeId(signedClaim.payload.iss)!,
1249-
)
1250-
: null;
1251-
const token = Token.fromSigned(signedClaim);
1252-
if (!token.verifyWithPublicKey(issPublicKey)) {
1253-
this.logger.warn('Failed to verify issuing node');
1254-
continue;
1255-
}
1256-
if (
1257-
subPublicKey != null &&
1258-
!token.verifyWithPublicKey(subPublicKey)
1259-
) {
1260-
this.logger.warn('Failed to verify subject node');
1261-
continue;
1262-
}
1263-
claims[claimId] = signedClaim;
1223+
return await this.withConnF(targetNodeId, ctx, async (connection) => {
1224+
const claims: Record<ClaimId, SignedClaim> = {};
1225+
const client = connection.getClient();
1226+
for await (const agentClaim of await client.methods.nodesClaimsGet({
1227+
claimIdEncoded:
1228+
claimId != null
1229+
? claimsUtils.encodeClaimId(claimId)
1230+
: ('' as ClaimIdEncoded),
1231+
})) {
1232+
if (ctx.signal.aborted) throw ctx.signal.reason;
1233+
// Need to re-construct each claim
1234+
const claimId: ClaimId = claimsUtils.decodeClaimId(
1235+
agentClaim.claimIdEncoded,
1236+
)!;
1237+
const signedClaimEncoded = agentClaim.signedTokenEncoded;
1238+
const signedClaim = claimsUtils.parseSignedClaim(signedClaimEncoded);
1239+
// Verifying the claim
1240+
const issPublicKey = keysUtils.publicKeyFromNodeId(
1241+
nodesUtils.decodeNodeId(signedClaim.payload.iss)!,
1242+
);
1243+
const subPublicKey =
1244+
signedClaim.payload.typ === 'node'
1245+
? keysUtils.publicKeyFromNodeId(
1246+
nodesUtils.decodeNodeId(signedClaim.payload.iss)!,
1247+
)
1248+
: null;
1249+
const token = Token.fromSigned(signedClaim);
1250+
if (!token.verifyWithPublicKey(issPublicKey)) {
1251+
this.logger.warn('Failed to verify issuing node');
1252+
continue;
12641253
}
1265-
return claims;
1266-
},
1267-
ctx,
1268-
);
1254+
if (subPublicKey != null && !token.verifyWithPublicKey(subPublicKey)) {
1255+
this.logger.warn('Failed to verify subject node');
1256+
continue;
1257+
}
1258+
claims[claimId] = signedClaim;
1259+
}
1260+
return claims;
1261+
});
12691262
}
12701263

12711264
/**
@@ -1296,83 +1289,79 @@ class NodeManager {
12961289
},
12971290
undefined,
12981291
async (token) => {
1299-
return this.withConnF(
1300-
targetNodeId,
1301-
async (conn) => {
1302-
// 2. create the agentClaim message to send
1303-
const halfSignedClaim = token.toSigned();
1304-
const halfSignedClaimEncoded =
1305-
claimsUtils.generateSignedClaim(halfSignedClaim);
1306-
const client = conn.getClient();
1307-
const stream = await client.methods.nodesCrossSignClaim();
1308-
const writer = stream.writable.getWriter();
1309-
const reader = stream.readable.getReader();
1310-
let fullySignedToken: Token<Claim>;
1311-
try {
1312-
await writer.write({
1313-
signedTokenEncoded: halfSignedClaimEncoded,
1314-
});
1315-
// 3. We expect to receive the doubly signed claim
1316-
const readStatus = await reader.read();
1317-
if (readStatus.done) {
1318-
throw new claimsErrors.ErrorEmptyStream();
1319-
}
1320-
const receivedClaim = readStatus.value;
1321-
// We need to re-construct the token from the message
1322-
const signedClaim = claimsUtils.parseSignedClaim(
1323-
receivedClaim.signedTokenEncoded,
1324-
);
1325-
fullySignedToken = Token.fromSigned(signedClaim);
1326-
// Check that the signatures are correct
1327-
const targetNodePublicKey =
1328-
keysUtils.publicKeyFromNodeId(targetNodeId);
1329-
if (
1330-
!fullySignedToken.verifyWithPublicKey(
1331-
this.keyRing.keyPair.publicKey,
1332-
) ||
1333-
!fullySignedToken.verifyWithPublicKey(targetNodePublicKey)
1334-
) {
1335-
throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed();
1336-
}
1292+
return this.withConnF(targetNodeId, ctx, async (conn) => {
1293+
// 2. create the agentClaim message to send
1294+
const halfSignedClaim = token.toSigned();
1295+
const halfSignedClaimEncoded =
1296+
claimsUtils.generateSignedClaim(halfSignedClaim);
1297+
const client = conn.getClient();
1298+
const stream = await client.methods.nodesCrossSignClaim();
1299+
const writer = stream.writable.getWriter();
1300+
const reader = stream.readable.getReader();
1301+
let fullySignedToken: Token<Claim>;
1302+
try {
1303+
await writer.write({
1304+
signedTokenEncoded: halfSignedClaimEncoded,
1305+
});
1306+
// 3. We expect to receive the doubly signed claim
1307+
const readStatus = await reader.read();
1308+
if (readStatus.done) {
1309+
throw new claimsErrors.ErrorEmptyStream();
1310+
}
1311+
const receivedClaim = readStatus.value;
1312+
// We need to re-construct the token from the message
1313+
const signedClaim = claimsUtils.parseSignedClaim(
1314+
receivedClaim.signedTokenEncoded,
1315+
);
1316+
fullySignedToken = Token.fromSigned(signedClaim);
1317+
// Check that the signatures are correct
1318+
const targetNodePublicKey =
1319+
keysUtils.publicKeyFromNodeId(targetNodeId);
1320+
if (
1321+
!fullySignedToken.verifyWithPublicKey(
1322+
this.keyRing.keyPair.publicKey,
1323+
) ||
1324+
!fullySignedToken.verifyWithPublicKey(targetNodePublicKey)
1325+
) {
1326+
throw new claimsErrors.ErrorDoublySignedClaimVerificationFailed();
1327+
}
13371328

1338-
// Next stage is to process the claim for the other node
1339-
const readStatus2 = await reader.read();
1340-
if (readStatus2.done) {
1341-
throw new claimsErrors.ErrorEmptyStream();
1342-
}
1343-
const receivedClaimRemote = readStatus2.value;
1344-
// We need to re-construct the token from the message
1345-
const signedClaimRemote = claimsUtils.parseSignedClaim(
1346-
receivedClaimRemote.signedTokenEncoded,
1347-
);
1348-
// This is a singly signed claim,
1349-
// we want to verify it before signing and sending back
1350-
const signedTokenRemote = Token.fromSigned(signedClaimRemote);
1351-
if (!signedTokenRemote.verifyWithPublicKey(targetNodePublicKey)) {
1352-
throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed();
1353-
}
1354-
signedTokenRemote.signWithPrivateKey(this.keyRing.keyPair);
1355-
// 4. X <- responds with double signing the X signed claim <- Y
1356-
const agentClaimedMessageRemote = claimsUtils.generateSignedClaim(
1357-
signedTokenRemote.toSigned(),
1358-
);
1359-
await writer.write({
1360-
signedTokenEncoded: agentClaimedMessageRemote,
1361-
});
1362-
1363-
// Check the stream is closed (should be closed by other side)
1364-
const finalResponse = await reader.read();
1365-
if (finalResponse.done != null) {
1366-
await writer.close();
1367-
}
1368-
} catch (e) {
1369-
await writer.abort(e);
1370-
throw e;
1329+
// Next stage is to process the claim for the other node
1330+
const readStatus2 = await reader.read();
1331+
if (readStatus2.done) {
1332+
throw new claimsErrors.ErrorEmptyStream();
13711333
}
1372-
return fullySignedToken;
1373-
},
1374-
ctx,
1375-
);
1334+
const receivedClaimRemote = readStatus2.value;
1335+
// We need to re-construct the token from the message
1336+
const signedClaimRemote = claimsUtils.parseSignedClaim(
1337+
receivedClaimRemote.signedTokenEncoded,
1338+
);
1339+
// This is a singly signed claim,
1340+
// we want to verify it before signing and sending back
1341+
const signedTokenRemote = Token.fromSigned(signedClaimRemote);
1342+
if (!signedTokenRemote.verifyWithPublicKey(targetNodePublicKey)) {
1343+
throw new claimsErrors.ErrorSinglySignedClaimVerificationFailed();
1344+
}
1345+
signedTokenRemote.signWithPrivateKey(this.keyRing.keyPair);
1346+
// 4. X <- responds with double signing the X signed claim <- Y
1347+
const agentClaimedMessageRemote = claimsUtils.generateSignedClaim(
1348+
signedTokenRemote.toSigned(),
1349+
);
1350+
await writer.write({
1351+
signedTokenEncoded: agentClaimedMessageRemote,
1352+
});
1353+
1354+
// Check the stream is closed (should be closed by other side)
1355+
const finalResponse = await reader.read();
1356+
if (finalResponse.done != null) {
1357+
await writer.close();
1358+
}
1359+
} catch (e) {
1360+
await writer.abort(e);
1361+
throw e;
1362+
}
1363+
return fullySignedToken;
1364+
});
13761365
},
13771366
tran,
13781367
);

src/notifications/NotificationsManager.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,16 @@ class NotificationsManager {
166166
);
167167
// The task id if a new task has been scheduled for a retry.
168168
try {
169-
await this.nodeManager.withConnF(nodeId, async (connection) => {
170-
const client = connection.getClient();
171-
await client.methods.notificationsSend({
172-
signedNotificationEncoded: signedNotification,
173-
});
174-
});
169+
await this.nodeManager.withConnF(
170+
nodeId,
171+
undefined,
172+
async (connection) => {
173+
const client = connection.getClient();
174+
await client.methods.notificationsSend({
175+
signedNotificationEncoded: signedNotification,
176+
});
177+
},
178+
);
175179
await this.db.del(notificationKeyPath);
176180
} catch (e) {
177181
this.logger.warn(

0 commit comments

Comments
 (0)