@peerbit/shared-log 16.0.12 → 16.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ import type { Entry } from "@peerbit/log";
2
+ import { equals as bytesEqual } from "uint8arrays";
3
+
4
+ export type ReceiveSignatureVerificationFact<T> = {
5
+ entry: Entry<T>;
6
+ storageBytes: Uint8Array;
7
+ verified: boolean;
8
+ };
9
+
10
+ export const receiveSignatureVerificationResult = <T>(
11
+ factsByEntry: WeakMap<Entry<T>, ReceiveSignatureVerificationFact<T>[]>,
12
+ entry: Entry<T>,
13
+ ): boolean | undefined => {
14
+ const facts = factsByEntry.get(entry);
15
+ if (!facts || facts.length === 0) {
16
+ return undefined;
17
+ }
18
+ let storageBytes: Uint8Array;
19
+ try {
20
+ storageBytes = entry.getStorageBytes();
21
+ } catch {
22
+ return undefined;
23
+ }
24
+ for (let i = facts.length - 1; i >= 0; i--) {
25
+ const fact = facts[i]!;
26
+ if (bytesEqual(storageBytes, fact.storageBytes)) {
27
+ return fact.verified;
28
+ }
29
+ }
30
+ return undefined;
31
+ };
32
+
33
+ export const withReceiveSignatureVerificationFacts = async <T, R>(
34
+ factsByEntry: WeakMap<Entry<T>, ReceiveSignatureVerificationFact<T>[]>,
35
+ facts: ReceiveSignatureVerificationFact<T>[] | undefined,
36
+ operation: () => Promise<R>,
37
+ ): Promise<R> => {
38
+ if (!facts || facts.length === 0) {
39
+ return operation();
40
+ }
41
+ for (const fact of facts) {
42
+ const active = factsByEntry.get(fact.entry);
43
+ if (active) {
44
+ active.push(fact);
45
+ } else {
46
+ factsByEntry.set(fact.entry, [fact]);
47
+ }
48
+ }
49
+ try {
50
+ return await operation();
51
+ } finally {
52
+ for (const fact of facts) {
53
+ const active = factsByEntry.get(fact.entry);
54
+ if (!active) {
55
+ continue;
56
+ }
57
+ const index = active.indexOf(fact);
58
+ if (index >= 0) {
59
+ active.splice(index, 1);
60
+ }
61
+ if (active.length === 0) {
62
+ factsByEntry.delete(fact.entry);
63
+ }
64
+ }
65
+ }
66
+ };
@@ -10,7 +10,9 @@ import {
10
10
  AddedReplicationSegmentMessage,
11
11
  AllReplicatingSegmentsMessage,
12
12
  FullReplicationInfoV2Message,
13
+ ReplicationInfoV2AppliedMessage,
13
14
  type ReplicationInfoV2Message,
15
+ RequestReplicationInfoV2AppliedMessage,
14
16
  RequestReplicationInfoV2Message,
15
17
  StoppedReplicating,
16
18
  StoppedReplicationInfoV2Message,
@@ -1250,6 +1252,46 @@ export class ReplicationInfoV2ReceiveCoordinator {
1250
1252
  return true;
1251
1253
  }
1252
1254
 
1255
+ /**
1256
+ * Answer only after the exact authenticated receive generation has committed
1257
+ * the queried sequence (or a newer one). No pending query is retained: the
1258
+ * sender's bounded retry loop reissues an authoritative snapshot and query.
1259
+ */
1260
+ confirmApplied(
1261
+ request: RequestReplicationInfoV2AppliedMessage,
1262
+ properties: {
1263
+ from: PublicSignKey;
1264
+ peerSession: object;
1265
+ receiveEpoch: object | null;
1266
+ senderTransportSession: bigint;
1267
+ },
1268
+ ): ReplicationInfoV2AppliedMessage | undefined {
1269
+ const state = this._receiveStates.get(properties.from.hashcode());
1270
+ if (
1271
+ !state ||
1272
+ state.peerSession !== properties.peerSession ||
1273
+ state.receiveEpoch !== properties.receiveEpoch ||
1274
+ state.senderTransportSession !== properties.senderTransportSession ||
1275
+ !state.target.equals(properties.from) ||
1276
+ !state.receiverBinding ||
1277
+ !bytesEqual(request.receiverChallenge, state.receiverBinding) ||
1278
+ !state.senderEpoch ||
1279
+ !bytesEqual(request.senderEpoch, state.senderEpoch) ||
1280
+ request.sequence <= 0n ||
1281
+ state.lastSequence === undefined ||
1282
+ state.lastSequence < request.sequence ||
1283
+ !this.isStateCurrent(state)
1284
+ ) {
1285
+ return undefined;
1286
+ }
1287
+ return new ReplicationInfoV2AppliedMessage({
1288
+ receiverChallenge: request.receiverChallenge.slice(),
1289
+ senderEpoch: request.senderEpoch.slice(),
1290
+ sequence: request.sequence,
1291
+ revision: request.revision,
1292
+ });
1293
+ }
1294
+
1253
1295
  requireFullAfterFailure(
1254
1296
  admission: ReplicationInfoV2ReceiveAdmission,
1255
1297
  ): boolean {