@peerbit/shared-log 16.0.11 → 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.
package/dist/src/index.js CHANGED
@@ -55,7 +55,7 @@ import { CoordinatePersistenceCoordinator, combineCoordinateDeleteHashes, isProm
55
55
  import { CPUUsageIntervalLag } from "./cpu.js";
56
56
  import { debouncedAccumulatorMap, } from "./debounce.js";
57
57
  import { CompatibilityModeRetiredError, NativeDurableCommitError, NoPeersError, isNotStartedError, } from "./errors.js";
58
- import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY, SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE, SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
58
+ import { EXCHANGE_HEADS_REPAIR_HINT, EntryWithRefs, ExchangeHeadsMessage, MAX_RAW_EXCHANGE_MESSAGE_SIZE, RawEntryWithRefs, RawExchangeHeadsMessage, RequestIPrune, RequestIPruneV2, ResponseIPrune, ResponseIPruneV2, SYNC_CAPABILITY_RAW_EXCHANGE_HEADS, SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY, SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM, SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE, SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND, StashBackedRawExchangeHeadsMessage, SyncCapabilitiesMessage, collectRawExchangeHeadSendPlan, createExchangeHeadsMessages, createRawExchangeHeadsMessages, getExchangeHeadHash, getPreparedRawExchangeGid, getPreparedRawExchangeHashNumber, getPreparedRawExchangeHeadAppendFacts, getPreparedRawExchangeHeadGid, getPreparedRawExchangeHeadRequestedReplicas, getPreparedRawExchangeHeadShallowEntry, getPreparedRawExchangeHeadSignatureVerified, getPreparedRawExchangeNext, getPreparedRawExchangeRequestedReplicas, getPreparedRawExchangeTimestamp, getRawExchangeHeadByteLength, getRawExchangeHeadStashIndexes, initExchangeHeadEntry, isPreparedRawEntryWithRefs, isStashBackedRawExchangeHeadsMessage, materializeVerifiedRawExchangeHeadsMessage, } from "./exchange-heads.js";
59
59
  import { FanoutEnvelope } from "./fanout-envelope.js";
60
60
  import { InstanceLifecycle } from "./instance-lifecycle.js";
61
61
  import { MAX_U32, MAX_U64, createNumbers, } from "./integers.js";
@@ -66,14 +66,15 @@ import { NativeBackboneWriteThroughBlockStore } from "./native-write-through-blo
66
66
  import { PeerSessionRegistry } from "./peer-session.js";
67
67
  import { PIDReplicationController } from "./pid.js";
68
68
  import { EntryReplicatedU32, EntryReplicatedU64, ReplicationIntent, ReplicationRangeIndexableU32, ReplicationRangeIndexableU64, ReplicationRangeMessage, appromixateCoverage, calculateCoverage, countCoveringRangesSameOwner, createAssignedRangesQuery, debounceAggregationChanges, getAllMergeCandiates, getCoverSet, getSamples, isEntryReplicated, isMatured, isReplicationRangeMessage, mergeRanges, minimumWidthToCover, shouldAssigneToRangeBoundary as shouldAssignToRangeBoundary, toRebalance, } from "./ranges.js";
69
+ import { receiveSignatureVerificationResult as getReceiveSignatureVerificationResult, withReceiveSignatureVerificationFacts as withSignatureVerificationFacts, } from "./receive-signature-verification.js";
69
70
  import { ReplicationAnnouncementCoordinator } from "./replication-announcement.js";
70
71
  import { createReplicationDomainHash, } from "./replication-domain-hash.js";
71
72
  import { createReplicationDomainTime, } from "./replication-domain-time.js";
72
73
  import {} from "./replication-domain.js";
73
74
  import { ReplicationInfoV2ReceiveCoordinator } from "./replication-info-v2-receive.js";
74
- import { ReplicationInfoV2SendCoordinator } from "./replication-info-v2-send.js";
75
+ import { ReplicationInfoV2SendCoordinator, } from "./replication-info-v2-send.js";
75
76
  import { classifyReplicationStatus, } from "./replication-status.js";
76
- import { AbsoluteReplicas, AddedReplicationInfoV2Message, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, FullReplicationInfoV2Message, MinReplicas, ReplicationError, ReplicationPingMessage, RequestReplicationInfoMessage, RequestReplicationInfoV2Message, ResponseRoleMessage, StoppedReplicating, StoppedReplicationInfoV2Message, decodeReplicas, encodeReplicas, isReplicationInfoV2Message, maxReplicas, } from "./replication.js";
77
+ import { AbsoluteReplicas, AddedReplicationInfoV2Message, AddedReplicationSegmentMessage, AllReplicatingSegmentsMessage, FullReplicationInfoV2Message, MinReplicas, ReplicationError, ReplicationInfoV2AppliedMessage, ReplicationPingMessage, RequestReplicationInfoMessage, RequestReplicationInfoV2AppliedMessage, RequestReplicationInfoV2Message, ResponseRoleMessage, StoppedReplicating, StoppedReplicationInfoV2Message, decodeReplicas, encodeReplicas, isReplicationInfoV2Message, maxReplicas, } from "./replication.js";
77
78
  import { ReplicatorLivenessMonitor } from "./replicator-liveness.js";
78
79
  import { createSyncronizer } from "./sync/factory.js";
79
80
  import { emitSyncProfileDuration, emitSyncProfileEvent, syncProfileStart, } from "./sync/profile.js";
@@ -747,6 +748,13 @@ let SharedLog = (() => {
747
748
  _providerHandle;
748
749
  _isTrustedReplicator;
749
750
  _logProperties;
751
+ // A successful native signature batch is only an authorization input for the
752
+ // receive operation that requested it. Facts retain the exact verified bytes
753
+ // and are leased around the lower-log join; byte comparison prevents an
754
+ // earlier application callback from mutating a later entry underneath the
755
+ // batch result. The array form also makes overlapping leases for the same
756
+ // object safe without making verification sticky across retries.
757
+ _receiveSignatureVerificationFacts = new WeakMap();
750
758
  _closeController;
751
759
  _respondToIHaveTimeout;
752
760
  _checkedPrune;
@@ -1815,12 +1823,18 @@ let SharedLog = (() => {
1815
1823
  this._peerSessions.isReceiveCleanupGateOpen(peerHash),
1816
1824
  captureReplicationOwnershipLifecycle: () => this.captureReplicationOwnershipLifecycle(),
1817
1825
  isReplicationOwnershipLifecycleActive: (controller) => this.isRepairLifecycleActive(controller),
1826
+ supportsApplicationConfirmation: (peerHash, receiverTransportSession) => this._peerSyncCapabilitySessions.get(peerHash) ===
1827
+ receiverTransportSession &&
1828
+ ((this._peerSyncCapabilities.get(peerHash) ?? 0) &
1829
+ SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !==
1830
+ 0,
1818
1831
  });
1819
1832
  }
1820
1833
  replicationInfoV2ReceiveCapabilities() {
1821
1834
  return (SYNC_CAPABILITY_REPLICATION_INFO_V2_DECODE |
1822
1835
  SYNC_CAPABILITY_REPLICATION_INFO_V2_SEND |
1823
1836
  SYNC_CAPABILITY_REPLICATION_INFO_V2_APPLY |
1837
+ SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM |
1824
1838
  (this._logProperties?.sync?.rawExchangeHeads === true
1825
1839
  ? SYNC_CAPABILITY_RAW_EXCHANGE_HEADS
1826
1840
  : 0));
@@ -3807,7 +3821,11 @@ let SharedLog = (() => {
3807
3821
  ? this.replicationController?.maxMemoryLimit
3808
3822
  : undefined;
3809
3823
  try {
3810
- return await this.applyReplicationRole(rangeOrEntry, options);
3824
+ const ranges = await this.applyReplicationRole(rangeOrEntry, options);
3825
+ if (options?.confirm) {
3826
+ await this._v2Send.confirmLatest(typeof options.confirm === "object" ? options.confirm : undefined);
3827
+ }
3828
+ return ranges;
3811
3829
  }
3812
3830
  finally {
3813
3831
  const storageObjective = this._isAdaptiveReplicating
@@ -3900,7 +3918,7 @@ let SharedLog = (() => {
3900
3918
  this.throwIfReplicationOwnershipLifecycleInactive(replicationOwnershipLifecycleController);
3901
3919
  return this._replicate(range, options, replicationOwnershipLifecycleController);
3902
3920
  }
3903
- async unreplicate(rangeOrEntry) {
3921
+ async unreplicate(rangeOrEntry, options) {
3904
3922
  const wasAdaptiveReplicating = this._isAdaptiveReplicating;
3905
3923
  const previousStorageObjective = wasAdaptiveReplicating
3906
3924
  ? this.replicationController?.maxMemoryLimit
@@ -3908,7 +3926,11 @@ let SharedLog = (() => {
3908
3926
  this.throwIfCheckedPruneRemoveBlocksLocalOperation("replication range mutation");
3909
3927
  const replicationOwnershipLifecycleController = this.captureReplicationOwnershipLifecycle();
3910
3928
  try {
3911
- return await this._unreplicate(rangeOrEntry, replicationOwnershipLifecycleController);
3929
+ const result = await this._unreplicate(rangeOrEntry, replicationOwnershipLifecycleController);
3930
+ if (options?.confirm) {
3931
+ await this._v2Send.confirmLatest(typeof options.confirm === "object" ? options.confirm : undefined);
3932
+ }
3933
+ return result;
3912
3934
  }
3913
3935
  finally {
3914
3936
  const storageObjective = this._isAdaptiveReplicating
@@ -9436,6 +9458,7 @@ let SharedLog = (() => {
9436
9458
  : undefined,
9437
9459
  };
9438
9460
  this._logProperties = options;
9461
+ this._receiveSignatureVerificationFacts = new WeakMap();
9439
9462
  this.domain = options?.domain
9440
9463
  ? options.domain(this)
9441
9464
  : createReplicationDomainHash("u64")(this);
@@ -11302,6 +11325,70 @@ let SharedLog = (() => {
11302
11325
  }
11303
11326
  });
11304
11327
  }
11328
+ receiveSignatureVerificationResult(entry) {
11329
+ return getReceiveSignatureVerificationResult(this._receiveSignatureVerificationFacts, entry);
11330
+ }
11331
+ async preverifyReceiveSignaturesBatch(entries, profile) {
11332
+ const candidates = entries.filter((entry) => !entry.createdLocally && !hasPreverifiedSignature(entry));
11333
+ if (candidates.length < NATIVE_ED25519_VERIFY_BATCH_MIN_ENTRIES) {
11334
+ return undefined;
11335
+ }
11336
+ let storageBytes;
11337
+ let signableInputs;
11338
+ try {
11339
+ // Capture the callback-visible storage state and native signable inputs
11340
+ // before the first await so the result is bound to one entry state.
11341
+ storageBytes = candidates.map((entry) => entry.getStorageBytes().slice());
11342
+ signableInputs =
11343
+ this.prepareReceiveNativeEd25519VerificationBatch(candidates);
11344
+ }
11345
+ catch {
11346
+ return undefined;
11347
+ }
11348
+ if (!signableInputs) {
11349
+ return undefined;
11350
+ }
11351
+ const verifyStartedAt = syncProfileStart(profile);
11352
+ let verified;
11353
+ try {
11354
+ verified = await verifyEd25519Batch(signableInputs);
11355
+ }
11356
+ catch {
11357
+ verified = undefined;
11358
+ }
11359
+ if (!verified || verified.length !== candidates.length) {
11360
+ return undefined;
11361
+ }
11362
+ if (profile) {
11363
+ emitSyncProfileDuration(profile, verifyStartedAt, {
11364
+ name: "sharedLog.canAppendBatch.verifySignatures",
11365
+ component: "shared-log",
11366
+ entries: candidates.length,
11367
+ messages: 1,
11368
+ details: {
11369
+ native: true,
11370
+ mode: "signable",
11371
+ programCanAppendDeferred: true,
11372
+ },
11373
+ });
11374
+ }
11375
+ return candidates.map((entry, index) => ({
11376
+ entry,
11377
+ storageBytes: storageBytes[index],
11378
+ verified: verified[index],
11379
+ }));
11380
+ }
11381
+ prepareReceiveNativeEd25519VerificationBatch(entries) {
11382
+ const inputs = this.prepareNativeEd25519VerificationBatch(entries);
11383
+ return inputs?.map((input) => ({
11384
+ signature: input.signature.slice(),
11385
+ publicKey: input.publicKey.slice(),
11386
+ message: input.message.slice(),
11387
+ }));
11388
+ }
11389
+ async withReceiveSignatureVerificationFacts(facts, operation) {
11390
+ return withSignatureVerificationFacts(this._receiveSignatureVerificationFacts, facts, operation);
11391
+ }
11305
11392
  async canAppend(entry) {
11306
11393
  try {
11307
11394
  if (!entry.meta.data) {
@@ -11313,9 +11400,16 @@ let SharedLog = (() => {
11313
11400
  return false;
11314
11401
  }
11315
11402
  checkMinReplicasLimit(replicas);
11316
- // Locally-created entries were signed before append.
11403
+ const receiveSignatureVerification = this.receiveSignatureVerificationResult(entry);
11404
+ if (receiveSignatureVerification === false) {
11405
+ return false;
11406
+ }
11407
+ // Locally-created entries were signed before append. Raw prepared entries
11408
+ // can carry a durable verifier fact from their decoder; plain receive facts
11409
+ // are scoped to this lower-log join and checked against the exact bytes.
11317
11410
  if (!entry.createdLocally &&
11318
11411
  !hasPreverifiedSignature(entry) &&
11412
+ receiveSignatureVerification !== true &&
11319
11413
  !(await entry.verifySignatures())) {
11320
11414
  return false;
11321
11415
  }
@@ -13453,6 +13547,9 @@ let SharedLog = (() => {
13453
13547
  // entry views.
13454
13548
  const programCanAppend = !!this._logProperties?.canAppend;
13455
13549
  const programOnChange = !!this._logProperties?.onChange;
13550
+ const receiveSignatureVerificationFacts = programCanAppend
13551
+ ? await this.preverifyReceiveSignaturesBatch(materializeAllToMergeEntries(), syncProfile)
13552
+ : undefined;
13456
13553
  const nativeBackboneCommitValidation = programCanAppend
13457
13554
  ? undefined
13458
13555
  : this.validatePreparedRawReceiveHeadsMetadataWithNativeBackbone(allToMerge, syncProfile, { decodedReplicaCounts: receiveReplicaCounts });
@@ -13617,29 +13714,34 @@ let SharedLog = (() => {
13617
13714
  const joinOnAppendHashes = programOnChange
13618
13715
  ? undefined
13619
13716
  : onAppendHashes;
13620
- const joinedPreparedFacts = canUsePreparedAppendFacts &&
13621
- (await trustedLowerLog.joinPreparedAppendFactsBatch(preparedAppendFacts, {
13622
- __peerbitEntriesAlreadyMissing: true,
13623
- __peerbitCanAppendAlreadyValidated: true,
13624
- __peerbitDeferIndexWrite: true,
13625
- __peerbitOnAppendHashes: joinOnAppendHashes,
13626
- __peerbitProfile: syncProfile,
13627
- __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
13628
- __peerbitNativePreparedJoinCommitValidatesPlan: nativePreparedJoinCommitValidatesPlan,
13629
- __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
13630
- ? finishNativePreparedCoordinates
13631
- : undefined,
13632
- }));
13633
- if (!joinedPreparedFacts) {
13634
- await trustedLowerLog.join(materializeAllToMergeEntries(), {
13635
- __peerbitBatchIndependent: true,
13636
- __peerbitEntriesAlreadyMissing: true,
13637
- __peerbitCanAppendAlreadyValidated: fallbackCanAppendAlreadyValidated,
13638
- __peerbitDeferIndexWrite: true,
13639
- __peerbitOnAppendHashes: joinOnAppendHashes,
13640
- __peerbitProfile: syncProfile,
13641
- });
13642
- }
13717
+ let joinedPreparedFacts = false;
13718
+ const joinLowerLog = async () => {
13719
+ joinedPreparedFacts =
13720
+ canUsePreparedAppendFacts &&
13721
+ (await trustedLowerLog.joinPreparedAppendFactsBatch(preparedAppendFacts, {
13722
+ __peerbitEntriesAlreadyMissing: true,
13723
+ __peerbitCanAppendAlreadyValidated: true,
13724
+ __peerbitDeferIndexWrite: true,
13725
+ __peerbitOnAppendHashes: joinOnAppendHashes,
13726
+ __peerbitProfile: syncProfile,
13727
+ __peerbitNativePreparedJoinCommit: nativePreparedJoinCommit,
13728
+ __peerbitNativePreparedJoinCommitValidatesPlan: nativePreparedJoinCommitValidatesPlan,
13729
+ __peerbitOnPreparedJoinCommitted: nativePreparedJoinCommit
13730
+ ? finishNativePreparedCoordinates
13731
+ : undefined,
13732
+ }));
13733
+ if (!joinedPreparedFacts) {
13734
+ await trustedLowerLog.join(materializeAllToMergeEntries(), {
13735
+ __peerbitBatchIndependent: true,
13736
+ __peerbitEntriesAlreadyMissing: true,
13737
+ __peerbitCanAppendAlreadyValidated: fallbackCanAppendAlreadyValidated,
13738
+ __peerbitDeferIndexWrite: true,
13739
+ __peerbitOnAppendHashes: joinOnAppendHashes,
13740
+ __peerbitProfile: syncProfile,
13741
+ });
13742
+ }
13743
+ };
13744
+ await this.withReceiveSignatureVerificationFacts(receiveSignatureVerificationFacts, joinLowerLog);
13643
13745
  // A recursive lower-log join can resolve successfully while declining
13644
13746
  // an individual top-level entry (for example, when one of its parents
13645
13747
  // is temporarily unavailable). The public Log.join() API intentionally
@@ -13897,6 +13999,42 @@ let SharedLog = (() => {
13897
13999
  this.clearRepairFrontierHashes(context.from.hashcode(), msg.hashes);
13898
14000
  return;
13899
14001
  }
14002
+ else if (msg instanceof RequestReplicationInfoV2AppliedMessage) {
14003
+ if (receiveSession?.phase === "open" &&
14004
+ this._peerSyncCapabilitySessions.get(receiveFromHash) ===
14005
+ context.message.header.session &&
14006
+ ((this._peerSyncCapabilities.get(receiveFromHash) ?? 0) &
14007
+ SYNC_CAPABILITY_REPLICATION_INFO_V2_CONFIRM) !==
14008
+ 0) {
14009
+ const applied = this._v2Receive.confirmApplied(msg, {
14010
+ from: context.from,
14011
+ peerSession: receiveSession,
14012
+ receiveEpoch: receiveReplicationInfoReceiveEpoch,
14013
+ senderTransportSession: context.message.header.session,
14014
+ });
14015
+ if (applied) {
14016
+ await this.rpc.send(applied, {
14017
+ mode: new SilentDelivery({
14018
+ to: [context.from],
14019
+ redundancy: 1,
14020
+ }),
14021
+ priority: CONVERGENCE_MESSAGE_PRIORITY,
14022
+ signal: AbortSignal.any([
14023
+ this._closeController.signal,
14024
+ lane.ownershipLifecycleController.signal,
14025
+ ]),
14026
+ });
14027
+ }
14028
+ }
14029
+ return;
14030
+ }
14031
+ else if (msg instanceof ReplicationInfoV2AppliedMessage) {
14032
+ this._v2Send.acceptApplied(msg, {
14033
+ from: context.from,
14034
+ receiverTransportSession: context.message.header.session,
14035
+ });
14036
+ return;
14037
+ }
13900
14038
  else if (isReplicationInfoV2Message(msg)) {
13901
14039
  await this.handleReplicationInfoV2Announcement(msg, laneRequestContext, lane);
13902
14040
  }