@peerbit/shared-log 16.0.12 → 16.0.14
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.
- package/README.md +14 -0
- package/dist/src/exchange-heads.d.ts +6 -0
- package/dist/src/exchange-heads.d.ts.map +1 -1
- package/dist/src/exchange-heads.js +6 -0
- package/dist/src/exchange-heads.js.map +1 -1
- package/dist/src/index.d.ts +13 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +178 -33
- package/dist/src/index.js.map +1 -1
- package/dist/src/receive-signature-verification.d.ts +9 -0
- package/dist/src/receive-signature-verification.d.ts.map +1 -0
- package/dist/src/receive-signature-verification.js +54 -0
- package/dist/src/receive-signature-verification.js.map +1 -0
- package/dist/src/replication-info-v2-receive.d.ts +12 -1
- package/dist/src/replication-info-v2-receive.d.ts.map +1 -1
- package/dist/src/replication-info-v2-receive.js +30 -1
- package/dist/src/replication-info-v2-receive.js.map +1 -1
- package/dist/src/replication-info-v2-send.d.ts +61 -1
- package/dist/src/replication-info-v2-send.d.ts.map +1 -1
- package/dist/src/replication-info-v2-send.js +335 -9
- package/dist/src/replication-info-v2-send.js.map +1 -1
- package/dist/src/replication.d.ts +31 -0
- package/dist/src/replication.d.ts.map +1 -1
- package/dist/src/replication.js +115 -0
- package/dist/src/replication.js.map +1 -1
- package/package.json +17 -15
- package/src/exchange-heads.ts +7 -0
- package/src/index.ts +240 -33
- package/src/receive-signature-verification.ts +66 -0
- package/src/replication-info-v2-receive.ts +42 -0
- package/src/replication-info-v2-send.ts +452 -11
- package/src/replication.ts +69 -0
package/src/index.ts
CHANGED
|
@@ -164,6 +164,7 @@ import {
|
|
|
164
164
|
ResponseIPruneV2,
|
|
165
165
|
SYNC_CAPABILITY_RAW_EXCHANGE_HEADS,
|
|
166
166
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY,
|
|
167
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM,
|
|
167
168
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE,
|
|
168
169
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND,
|
|
169
170
|
StashBackedRawExchangeHeadsMessage,
|
|
@@ -231,6 +232,11 @@ import {
|
|
|
231
232
|
shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary,
|
|
232
233
|
toRebalance,
|
|
233
234
|
} from "./ranges.js";
|
|
235
|
+
import {
|
|
236
|
+
type ReceiveSignatureVerificationFact,
|
|
237
|
+
receiveSignatureVerificationResult as getReceiveSignatureVerificationResult,
|
|
238
|
+
withReceiveSignatureVerificationFacts as withSignatureVerificationFacts,
|
|
239
|
+
} from "./receive-signature-verification.js";
|
|
234
240
|
import { ReplicationAnnouncementCoordinator } from "./replication-announcement.js";
|
|
235
241
|
import {
|
|
236
242
|
type ReplicationDomainHash,
|
|
@@ -251,7 +257,10 @@ import type {
|
|
|
251
257
|
FullReplicationInfoMutation,
|
|
252
258
|
} from "./replication-info-mutation.js";
|
|
253
259
|
import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-receive.js";
|
|
254
|
-
import {
|
|
260
|
+
import {
|
|
261
|
+
type ReplicationInfoV2ConfirmationOptions,
|
|
262
|
+
ReplicationInfoV2SendCoordinator,
|
|
263
|
+
} from "./replication-info-v2-send.js";
|
|
255
264
|
import {
|
|
256
265
|
type ReplicationStatus,
|
|
257
266
|
classifyReplicationStatus,
|
|
@@ -264,10 +273,12 @@ import {
|
|
|
264
273
|
FullReplicationInfoV2Message,
|
|
265
274
|
MinReplicas,
|
|
266
275
|
ReplicationError,
|
|
276
|
+
ReplicationInfoV2AppliedMessage,
|
|
267
277
|
type ReplicationInfoV2Message,
|
|
268
278
|
type ReplicationLimits,
|
|
269
279
|
ReplicationPingMessage,
|
|
270
280
|
RequestReplicationInfoMessage,
|
|
281
|
+
RequestReplicationInfoV2AppliedMessage,
|
|
271
282
|
RequestReplicationInfoV2Message,
|
|
272
283
|
ResponseRoleMessage,
|
|
273
284
|
StoppedReplicating,
|
|
@@ -487,25 +498,36 @@ const joinNativeCoordinateDirectory = (
|
|
|
487
498
|
`${nodeDirectory.replace(/[/\\]+$/, "")}/coordinates/${fsSafeLogId}`;
|
|
488
499
|
|
|
489
500
|
type DurableBlockSublevelStore = {
|
|
501
|
+
readonly supportsCrashSafeJournalCheckpoint?: boolean;
|
|
490
502
|
sublevel(
|
|
491
503
|
name: string,
|
|
492
504
|
options?: {
|
|
493
505
|
compactOnClose?: boolean;
|
|
494
506
|
compactOnCloseMinJournalBytes?: number;
|
|
507
|
+
compactMaxJournalBytes?: number;
|
|
495
508
|
durability?: "normal" | "strict";
|
|
496
509
|
},
|
|
497
510
|
): MaybePromise<AnyStore>;
|
|
498
511
|
};
|
|
499
512
|
|
|
513
|
+
const defaultNativeEntryBlockCompactMaxJournalBytes = 64 * 1024 * 1024;
|
|
514
|
+
|
|
500
515
|
const createNativeDurableBlockStore = async (
|
|
501
516
|
storage: DurableBlockSublevelStore,
|
|
502
517
|
): Promise<AnyBlockStore> =>
|
|
503
518
|
new AnyBlockStore(
|
|
504
519
|
await storage.sublevel("blocks", {
|
|
505
|
-
// Strict mirrors remain WAL-backed
|
|
506
|
-
//
|
|
507
|
-
//
|
|
520
|
+
// Strict mirrors remain WAL-backed. On POSIX Node, checkpoint only the
|
|
521
|
+
// historical suffix through the Rust store's fsync + atomic-rename path;
|
|
522
|
+
// browsers/custom backends and Windows retain the prior unbounded WAL until
|
|
523
|
+
// they expose an equally strong directory durability barrier.
|
|
508
524
|
compactOnClose: false,
|
|
525
|
+
...(storage.supportsCrashSafeJournalCheckpoint === true
|
|
526
|
+
? {
|
|
527
|
+
compactMaxJournalBytes:
|
|
528
|
+
defaultNativeEntryBlockCompactMaxJournalBytes,
|
|
529
|
+
}
|
|
530
|
+
: {}),
|
|
509
531
|
// A native append is acknowledged only after this mirror resolves. The
|
|
510
532
|
// Rust store's normal immutable fast path may resolve before its WAL write;
|
|
511
533
|
// strict mode waits for the journal write and sync, closing the SIGKILL gap.
|
|
@@ -1065,6 +1087,10 @@ export type ReplicationOptions<R extends "u32" | "u64" = any> =
|
|
|
1065
1087
|
| NewReplicationOptions<R>
|
|
1066
1088
|
| ExistingReplicationOptions<R>;
|
|
1067
1089
|
|
|
1090
|
+
/** Opt-in proof that a local replication-role update applied remotely. */
|
|
1091
|
+
export type ReplicationConfirmationOptions =
|
|
1092
|
+
ReplicationInfoV2ConfirmationOptions;
|
|
1093
|
+
|
|
1068
1094
|
export { BlocksMessage };
|
|
1069
1095
|
|
|
1070
1096
|
const isAdaptiveReplicatorOption = (
|
|
@@ -2179,6 +2205,16 @@ export class SharedLog<
|
|
|
2179
2205
|
private _logProperties?: LogProperties<T> &
|
|
2180
2206
|
LogEvents<T> &
|
|
2181
2207
|
SharedLogOptions<T, D, R>;
|
|
2208
|
+
// A successful native signature batch is only an authorization input for the
|
|
2209
|
+
// receive operation that requested it. Facts retain the exact verified bytes
|
|
2210
|
+
// and are leased around the lower-log join; byte comparison prevents an
|
|
2211
|
+
// earlier application callback from mutating a later entry underneath the
|
|
2212
|
+
// batch result. The array form also makes overlapping leases for the same
|
|
2213
|
+
// object safe without making verification sticky across retries.
|
|
2214
|
+
private _receiveSignatureVerificationFacts = new WeakMap<
|
|
2215
|
+
Entry<T>,
|
|
2216
|
+
ReceiveSignatureVerificationFact<T>[]
|
|
2217
|
+
>();
|
|
2182
2218
|
private _closeController!: AbortController;
|
|
2183
2219
|
private _respondToIHaveTimeout!: any;
|
|
2184
2220
|
private _checkedPrune!: CheckedPruneCoordinator<T, R>;
|
|
@@ -3561,6 +3597,12 @@ export class SharedLog<
|
|
|
3561
3597
|
this.captureReplicationOwnershipLifecycle(),
|
|
3562
3598
|
isReplicationOwnershipLifecycleActive: (controller) =>
|
|
3563
3599
|
this.isRepairLifecycleActive(controller),
|
|
3600
|
+
supportsApplicationConfirmation: (peerHash, receiverTransportSession) =>
|
|
3601
|
+
this._peerSyncCapabilitySessions.get(peerHash) ===
|
|
3602
|
+
receiverTransportSession &&
|
|
3603
|
+
((this._peerSyncCapabilities.get(peerHash) ?? 0) &
|
|
3604
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !==
|
|
3605
|
+
0,
|
|
3564
3606
|
});
|
|
3565
3607
|
}
|
|
3566
3608
|
|
|
@@ -3569,6 +3611,7 @@ export class SharedLog<
|
|
|
3569
3611
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
|
|
3570
3612
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
|
|
3571
3613
|
SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY |
|
|
3614
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM |
|
|
3572
3615
|
(this._logProperties?.sync?.rawExchangeHeads === true
|
|
3573
3616
|
? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
|
|
3574
3617
|
: 0)
|
|
@@ -6187,6 +6230,8 @@ export class SharedLog<
|
|
|
6187
6230
|
checkDuplicates?: boolean;
|
|
6188
6231
|
rebalance?: boolean;
|
|
6189
6232
|
mergeSegments?: boolean;
|
|
6233
|
+
/** Wait for explicit remote durable application; omitted is unchanged. */
|
|
6234
|
+
confirm?: boolean | ReplicationConfirmationOptions;
|
|
6190
6235
|
announce?: (
|
|
6191
6236
|
msg: FullReplicationInfoMutation | AddedReplicationInfoMutation,
|
|
6192
6237
|
) => void;
|
|
@@ -6197,7 +6242,13 @@ export class SharedLog<
|
|
|
6197
6242
|
? this.replicationController?.maxMemoryLimit
|
|
6198
6243
|
: undefined;
|
|
6199
6244
|
try {
|
|
6200
|
-
|
|
6245
|
+
const ranges = await this.applyReplicationRole(rangeOrEntry, options);
|
|
6246
|
+
if (options?.confirm) {
|
|
6247
|
+
await this._v2Send.confirmLatest(
|
|
6248
|
+
typeof options.confirm === "object" ? options.confirm : undefined,
|
|
6249
|
+
);
|
|
6250
|
+
}
|
|
6251
|
+
return ranges;
|
|
6201
6252
|
} finally {
|
|
6202
6253
|
const storageObjective = this._isAdaptiveReplicating
|
|
6203
6254
|
? this.replicationController?.maxMemoryLimit
|
|
@@ -6329,7 +6380,10 @@ export class SharedLog<
|
|
|
6329
6380
|
);
|
|
6330
6381
|
}
|
|
6331
6382
|
|
|
6332
|
-
async unreplicate(
|
|
6383
|
+
async unreplicate(
|
|
6384
|
+
rangeOrEntry?: Entry<T> | { id: Uint8Array }[],
|
|
6385
|
+
options?: { confirm?: boolean | ReplicationConfirmationOptions },
|
|
6386
|
+
) {
|
|
6333
6387
|
const wasAdaptiveReplicating = this._isAdaptiveReplicating;
|
|
6334
6388
|
const previousStorageObjective = wasAdaptiveReplicating
|
|
6335
6389
|
? this.replicationController?.maxMemoryLimit
|
|
@@ -6340,10 +6394,16 @@ export class SharedLog<
|
|
|
6340
6394
|
const replicationOwnershipLifecycleController =
|
|
6341
6395
|
this.captureReplicationOwnershipLifecycle();
|
|
6342
6396
|
try {
|
|
6343
|
-
|
|
6397
|
+
const result = await this._unreplicate(
|
|
6344
6398
|
rangeOrEntry,
|
|
6345
6399
|
replicationOwnershipLifecycleController,
|
|
6346
6400
|
);
|
|
6401
|
+
if (options?.confirm) {
|
|
6402
|
+
await this._v2Send.confirmLatest(
|
|
6403
|
+
typeof options.confirm === "object" ? options.confirm : undefined,
|
|
6404
|
+
);
|
|
6405
|
+
}
|
|
6406
|
+
return result;
|
|
6347
6407
|
} finally {
|
|
6348
6408
|
const storageObjective = this._isAdaptiveReplicating
|
|
6349
6409
|
? this.replicationController?.maxMemoryLimit
|
|
@@ -14600,6 +14660,7 @@ export class SharedLog<
|
|
|
14600
14660
|
: undefined,
|
|
14601
14661
|
};
|
|
14602
14662
|
this._logProperties = options;
|
|
14663
|
+
this._receiveSignatureVerificationFacts = new WeakMap();
|
|
14603
14664
|
|
|
14604
14665
|
this.domain = options?.domain
|
|
14605
14666
|
? (options.domain(this) as unknown as D)
|
|
@@ -17039,6 +17100,93 @@ export class SharedLog<
|
|
|
17039
17100
|
});
|
|
17040
17101
|
}
|
|
17041
17102
|
|
|
17103
|
+
private receiveSignatureVerificationResult(
|
|
17104
|
+
entry: Entry<T>,
|
|
17105
|
+
): boolean | undefined {
|
|
17106
|
+
return getReceiveSignatureVerificationResult(
|
|
17107
|
+
this._receiveSignatureVerificationFacts,
|
|
17108
|
+
entry,
|
|
17109
|
+
);
|
|
17110
|
+
}
|
|
17111
|
+
|
|
17112
|
+
private async preverifyReceiveSignaturesBatch(
|
|
17113
|
+
entries: Entry<T>[],
|
|
17114
|
+
profile?: SyncProfileFn,
|
|
17115
|
+
): Promise<ReceiveSignatureVerificationFact<T>[] | undefined> {
|
|
17116
|
+
const candidates = entries.filter(
|
|
17117
|
+
(entry) => !entry.createdLocally && !hasPreverifiedSignature(entry),
|
|
17118
|
+
);
|
|
17119
|
+
if (candidates.length < NATIVE_ED25519_VERIFY_BATCH_MIN_ENTRIES) {
|
|
17120
|
+
return undefined;
|
|
17121
|
+
}
|
|
17122
|
+
|
|
17123
|
+
let storageBytes: Uint8Array[];
|
|
17124
|
+
let signableInputs: Ed25519VerifyBatchInput[] | undefined;
|
|
17125
|
+
try {
|
|
17126
|
+
// Capture the callback-visible storage state and native signable inputs
|
|
17127
|
+
// before the first await so the result is bound to one entry state.
|
|
17128
|
+
storageBytes = candidates.map((entry) => entry.getStorageBytes().slice());
|
|
17129
|
+
signableInputs =
|
|
17130
|
+
this.prepareReceiveNativeEd25519VerificationBatch(candidates);
|
|
17131
|
+
} catch {
|
|
17132
|
+
return undefined;
|
|
17133
|
+
}
|
|
17134
|
+
if (!signableInputs) {
|
|
17135
|
+
return undefined;
|
|
17136
|
+
}
|
|
17137
|
+
|
|
17138
|
+
const verifyStartedAt = syncProfileStart(profile);
|
|
17139
|
+
let verified: boolean[] | undefined;
|
|
17140
|
+
try {
|
|
17141
|
+
verified = await verifyEd25519Batch(signableInputs);
|
|
17142
|
+
} catch {
|
|
17143
|
+
verified = undefined;
|
|
17144
|
+
}
|
|
17145
|
+
if (!verified || verified.length !== candidates.length) {
|
|
17146
|
+
return undefined;
|
|
17147
|
+
}
|
|
17148
|
+
if (profile) {
|
|
17149
|
+
emitSyncProfileDuration(profile, verifyStartedAt, {
|
|
17150
|
+
name: "sharedLog.canAppendBatch.verifySignatures",
|
|
17151
|
+
component: "shared-log",
|
|
17152
|
+
entries: candidates.length,
|
|
17153
|
+
messages: 1,
|
|
17154
|
+
details: {
|
|
17155
|
+
native: true,
|
|
17156
|
+
mode: "signable",
|
|
17157
|
+
programCanAppendDeferred: true,
|
|
17158
|
+
},
|
|
17159
|
+
});
|
|
17160
|
+
}
|
|
17161
|
+
return candidates.map((entry, index) => ({
|
|
17162
|
+
entry,
|
|
17163
|
+
storageBytes: storageBytes[index]!,
|
|
17164
|
+
verified: verified[index]!,
|
|
17165
|
+
}));
|
|
17166
|
+
}
|
|
17167
|
+
|
|
17168
|
+
private prepareReceiveNativeEd25519VerificationBatch(
|
|
17169
|
+
entries: Entry<T>[],
|
|
17170
|
+
): Ed25519VerifyBatchInput[] | undefined {
|
|
17171
|
+
const inputs = this.prepareNativeEd25519VerificationBatch(entries);
|
|
17172
|
+
return inputs?.map((input) => ({
|
|
17173
|
+
signature: input.signature.slice(),
|
|
17174
|
+
publicKey: input.publicKey.slice(),
|
|
17175
|
+
message: input.message.slice(),
|
|
17176
|
+
}));
|
|
17177
|
+
}
|
|
17178
|
+
|
|
17179
|
+
private async withReceiveSignatureVerificationFacts<R>(
|
|
17180
|
+
facts: ReceiveSignatureVerificationFact<T>[] | undefined,
|
|
17181
|
+
operation: () => Promise<R>,
|
|
17182
|
+
): Promise<R> {
|
|
17183
|
+
return withSignatureVerificationFacts(
|
|
17184
|
+
this._receiveSignatureVerificationFacts,
|
|
17185
|
+
facts,
|
|
17186
|
+
operation,
|
|
17187
|
+
);
|
|
17188
|
+
}
|
|
17189
|
+
|
|
17042
17190
|
async canAppend(entry: Entry<T>) {
|
|
17043
17191
|
try {
|
|
17044
17192
|
if (!entry.meta.data) {
|
|
@@ -17052,10 +17200,19 @@ export class SharedLog<
|
|
|
17052
17200
|
|
|
17053
17201
|
checkMinReplicasLimit(replicas);
|
|
17054
17202
|
|
|
17055
|
-
|
|
17203
|
+
const receiveSignatureVerification =
|
|
17204
|
+
this.receiveSignatureVerificationResult(entry);
|
|
17205
|
+
if (receiveSignatureVerification === false) {
|
|
17206
|
+
return false;
|
|
17207
|
+
}
|
|
17208
|
+
|
|
17209
|
+
// Locally-created entries were signed before append. Raw prepared entries
|
|
17210
|
+
// can carry a durable verifier fact from their decoder; plain receive facts
|
|
17211
|
+
// are scoped to this lower-log join and checked against the exact bytes.
|
|
17056
17212
|
if (
|
|
17057
17213
|
!entry.createdLocally &&
|
|
17058
17214
|
!hasPreverifiedSignature(entry) &&
|
|
17215
|
+
receiveSignatureVerification !== true &&
|
|
17059
17216
|
!(await entry.verifySignatures())
|
|
17060
17217
|
) {
|
|
17061
17218
|
return false;
|
|
@@ -19608,6 +19765,12 @@ export class SharedLog<
|
|
|
19608
19765
|
// entry views.
|
|
19609
19766
|
const programCanAppend = !!this._logProperties?.canAppend;
|
|
19610
19767
|
const programOnChange = !!this._logProperties?.onChange;
|
|
19768
|
+
const receiveSignatureVerificationFacts = programCanAppend
|
|
19769
|
+
? await this.preverifyReceiveSignaturesBatch(
|
|
19770
|
+
materializeAllToMergeEntries(),
|
|
19771
|
+
syncProfile,
|
|
19772
|
+
)
|
|
19773
|
+
: undefined;
|
|
19611
19774
|
const nativeBackboneCommitValidation = programCanAppend
|
|
19612
19775
|
? undefined
|
|
19613
19776
|
: this.validatePreparedRawReceiveHeadsMetadataWithNativeBackbone(
|
|
@@ -19811,35 +19974,43 @@ export class SharedLog<
|
|
|
19811
19974
|
const joinOnAppendHashes = programOnChange
|
|
19812
19975
|
? undefined
|
|
19813
19976
|
: onAppendHashes;
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19977
|
+
let joinedPreparedFacts = false;
|
|
19978
|
+
const joinLowerLog = async () => {
|
|
19979
|
+
joinedPreparedFacts =
|
|
19980
|
+
canUsePreparedAppendFacts &&
|
|
19981
|
+
(await trustedLowerLog.joinPreparedAppendFactsBatch(
|
|
19982
|
+
preparedAppendFacts,
|
|
19983
|
+
{
|
|
19984
|
+
__peerbitEntriesAlreadyMissing: true,
|
|
19985
|
+
__peerbitCanAppendAlreadyValidated: true,
|
|
19986
|
+
__peerbitDeferIndexWrite: true,
|
|
19987
|
+
__peerbitOnAppendHashes: joinOnAppendHashes,
|
|
19988
|
+
__peerbitProfile: syncProfile,
|
|
19989
|
+
__peerbitNativePreparedJoinCommit:
|
|
19990
|
+
nativePreparedJoinCommit,
|
|
19991
|
+
__peerbitNativePreparedJoinCommitValidatesPlan:
|
|
19992
|
+
nativePreparedJoinCommitValidatesPlan,
|
|
19993
|
+
__peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
|
|
19994
|
+
? finishNativePreparedCoordinates
|
|
19995
|
+
: undefined,
|
|
19996
|
+
},
|
|
19997
|
+
));
|
|
19998
|
+
if (!joinedPreparedFacts) {
|
|
19999
|
+
await trustedLowerLog.join(materializeAllToMergeEntries(), {
|
|
20000
|
+
__peerbitBatchIndependent: true,
|
|
19819
20001
|
__peerbitEntriesAlreadyMissing: true,
|
|
19820
|
-
__peerbitCanAppendAlreadyValidated:
|
|
20002
|
+
__peerbitCanAppendAlreadyValidated:
|
|
20003
|
+
fallbackCanAppendAlreadyValidated,
|
|
19821
20004
|
__peerbitDeferIndexWrite: true,
|
|
19822
20005
|
__peerbitOnAppendHashes: joinOnAppendHashes,
|
|
19823
20006
|
__peerbitProfile: syncProfile,
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
));
|
|
19832
|
-
if (!joinedPreparedFacts) {
|
|
19833
|
-
await trustedLowerLog.join(materializeAllToMergeEntries(), {
|
|
19834
|
-
__peerbitBatchIndependent: true,
|
|
19835
|
-
__peerbitEntriesAlreadyMissing: true,
|
|
19836
|
-
__peerbitCanAppendAlreadyValidated:
|
|
19837
|
-
fallbackCanAppendAlreadyValidated,
|
|
19838
|
-
__peerbitDeferIndexWrite: true,
|
|
19839
|
-
__peerbitOnAppendHashes: joinOnAppendHashes,
|
|
19840
|
-
__peerbitProfile: syncProfile,
|
|
19841
|
-
});
|
|
19842
|
-
}
|
|
20007
|
+
});
|
|
20008
|
+
}
|
|
20009
|
+
};
|
|
20010
|
+
await this.withReceiveSignatureVerificationFacts(
|
|
20011
|
+
receiveSignatureVerificationFacts,
|
|
20012
|
+
joinLowerLog,
|
|
20013
|
+
);
|
|
19843
20014
|
// A recursive lower-log join can resolve successfully while declining
|
|
19844
20015
|
// an individual top-level entry (for example, when one of its parents
|
|
19845
20016
|
// is temporarily unavailable). The public Log.join() API intentionally
|
|
@@ -20153,6 +20324,42 @@ export class SharedLog<
|
|
|
20153
20324
|
this.markEntriesKnownByPeer(msg.hashes, context.from.hashcode());
|
|
20154
20325
|
this.clearRepairFrontierHashes(context.from.hashcode(), msg.hashes);
|
|
20155
20326
|
return;
|
|
20327
|
+
} else if (msg instanceof RequestReplicationInfoV2AppliedMessage) {
|
|
20328
|
+
if (
|
|
20329
|
+
receiveSession?.phase === "open" &&
|
|
20330
|
+
this._peerSyncCapabilitySessions.get(receiveFromHash) ===
|
|
20331
|
+
context.message.header.session &&
|
|
20332
|
+
((this._peerSyncCapabilities.get(receiveFromHash) ?? 0) &
|
|
20333
|
+
SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !==
|
|
20334
|
+
0
|
|
20335
|
+
) {
|
|
20336
|
+
const applied = this._v2Receive.confirmApplied(msg, {
|
|
20337
|
+
from: context.from,
|
|
20338
|
+
peerSession: receiveSession,
|
|
20339
|
+
receiveEpoch: receiveReplicationInfoReceiveEpoch,
|
|
20340
|
+
senderTransportSession: context.message.header.session,
|
|
20341
|
+
});
|
|
20342
|
+
if (applied) {
|
|
20343
|
+
await this.rpc.send(applied, {
|
|
20344
|
+
mode: new SilentDelivery({
|
|
20345
|
+
to: [context.from],
|
|
20346
|
+
redundancy: 1,
|
|
20347
|
+
}),
|
|
20348
|
+
priority: CONVERGENCE_MESSAGE_PRIORITY,
|
|
20349
|
+
signal: AbortSignal.any([
|
|
20350
|
+
this._closeController.signal,
|
|
20351
|
+
lane.ownershipLifecycleController.signal,
|
|
20352
|
+
]),
|
|
20353
|
+
});
|
|
20354
|
+
}
|
|
20355
|
+
}
|
|
20356
|
+
return;
|
|
20357
|
+
} else if (msg instanceof ReplicationInfoV2AppliedMessage) {
|
|
20358
|
+
this._v2Send.acceptApplied(msg, {
|
|
20359
|
+
from: context.from,
|
|
20360
|
+
receiverTransportSession: context.message.header.session,
|
|
20361
|
+
});
|
|
20362
|
+
return;
|
|
20156
20363
|
} else if (isReplicationInfoV2Message(msg)) {
|
|
20157
20364
|
await this.handleReplicationInfoV2Announcement(
|
|
20158
20365
|
msg,
|
|
@@ -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 {
|