@peerbit/shared-log 9.0.10 → 9.1.0

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.
Files changed (44) hide show
  1. package/dist/benchmark/index.js +2 -2
  2. package/dist/benchmark/index.js.map +1 -1
  3. package/dist/benchmark/replication.js +3 -3
  4. package/dist/benchmark/replication.js.map +1 -1
  5. package/dist/src/index.d.ts +46 -31
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +426 -229
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/pid.d.ts.map +1 -1
  10. package/dist/src/pid.js +20 -19
  11. package/dist/src/pid.js.map +1 -1
  12. package/dist/src/ranges.d.ts +13 -3
  13. package/dist/src/ranges.d.ts.map +1 -1
  14. package/dist/src/ranges.js +207 -335
  15. package/dist/src/ranges.js.map +1 -1
  16. package/dist/src/replication-domain-hash.d.ts +5 -0
  17. package/dist/src/replication-domain-hash.d.ts.map +1 -0
  18. package/dist/src/replication-domain-hash.js +30 -0
  19. package/dist/src/replication-domain-hash.js.map +1 -0
  20. package/dist/src/replication-domain-time.d.ts +14 -0
  21. package/dist/src/replication-domain-time.d.ts.map +1 -0
  22. package/dist/src/replication-domain-time.js +59 -0
  23. package/dist/src/replication-domain-time.js.map +1 -0
  24. package/dist/src/replication-domain.d.ts +33 -0
  25. package/dist/src/replication-domain.d.ts.map +1 -0
  26. package/dist/src/replication-domain.js +6 -0
  27. package/dist/src/replication-domain.js.map +1 -0
  28. package/dist/src/replication.d.ts +10 -8
  29. package/dist/src/replication.d.ts.map +1 -1
  30. package/dist/src/replication.js +64 -46
  31. package/dist/src/replication.js.map +1 -1
  32. package/dist/src/role.d.ts +2 -1
  33. package/dist/src/role.d.ts.map +1 -1
  34. package/dist/src/role.js +6 -5
  35. package/dist/src/role.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/index.ts +604 -310
  38. package/src/pid.ts +20 -19
  39. package/src/ranges.ts +291 -371
  40. package/src/replication-domain-hash.ts +43 -0
  41. package/src/replication-domain-time.ts +85 -0
  42. package/src/replication-domain.ts +50 -0
  43. package/src/replication.ts +50 -46
  44. package/src/role.ts +6 -5
@@ -0,0 +1,43 @@
1
+ import { BinaryReader, BinaryWriter } from "@dao-xyz/borsh";
2
+ import { sha256 } from "@peerbit/crypto";
3
+ import type { ShallowOrFullEntry } from "@peerbit/log";
4
+ import {
5
+ type Log,
6
+ type ReplicationDomain,
7
+ type ReplicationDomainMapper,
8
+ } from "./replication-domain.js";
9
+
10
+ export const hashToU32 = (hash: Uint8Array) => {
11
+ const seedNumber = new BinaryReader(
12
+ hash.subarray(hash.length - 4, hash.length),
13
+ ).u32();
14
+ return seedNumber;
15
+ };
16
+
17
+ const hashTransformer: ReplicationDomainMapper<any> = async (
18
+ entry: ShallowOrFullEntry<any>,
19
+ ) => {
20
+ // For a fixed set or members, the choosen leaders will always be the same (address invariant)
21
+ // This allows for that same content is always chosen to be distributed to same peers, to remove unecessary copies
22
+
23
+ // Convert this thing we wan't to distribute to 8 bytes so we get can convert it into a u64
24
+ // modulus into an index
25
+ const utf8writer = new BinaryWriter();
26
+ utf8writer.string(entry.meta.gid);
27
+ const seed = await sha256(utf8writer.finalize());
28
+
29
+ // convert hash of slot to a number
30
+ return hashToU32(seed);
31
+ };
32
+ export type ReplicationDomainHash = ReplicationDomain<undefined, any>;
33
+ export const createReplicationDomainHash: () => ReplicationDomainHash = () => {
34
+ return {
35
+ type: "hash",
36
+ fromEntry: hashTransformer,
37
+ fromArgs: async (args: undefined, log: Log) => {
38
+ return {
39
+ offset: log.node.identity.publicKey,
40
+ };
41
+ },
42
+ };
43
+ };
@@ -0,0 +1,85 @@
1
+ import type { ShallowOrFullEntry } from "@peerbit/log";
2
+ import {
3
+ type ReplicationDomain,
4
+ type ReplicationDomainMapper,
5
+ type u32,
6
+ } from "./replication-domain.js";
7
+
8
+ type TimeUnit = "seconds" | "milliseconds" | "microseconds" | "nanoseconds";
9
+
10
+ const scalarNanoToUnit = {
11
+ seconds: BigInt(1e9),
12
+ milliseconds: BigInt(1e6),
13
+ microseconds: BigInt(1e3),
14
+ nanoseconds: BigInt(1),
15
+ };
16
+ const scalarMilliToUnit = {
17
+ seconds: 1e3,
18
+ milliseconds: 1,
19
+ microseconds: 1e-3,
20
+ nanoseconds: 1e-6,
21
+ };
22
+
23
+ export const fromEntry = (
24
+ origin: Date,
25
+ unit: TimeUnit = "milliseconds",
26
+ ): ReplicationDomainMapper<any> => {
27
+ const scalar = scalarNanoToUnit[unit];
28
+ const originTime = +origin / scalarMilliToUnit[unit];
29
+
30
+ const fn = (entry: ShallowOrFullEntry<any>) => {
31
+ const cursor = entry.meta.clock.timestamp.wallTime / scalar;
32
+ return Math.round(Number(cursor) - originTime);
33
+ };
34
+ return fn;
35
+ };
36
+
37
+ type TimeRange = { from: number; to: number };
38
+
39
+ export type ReplicationDomainTime = ReplicationDomain<TimeRange, any> & {
40
+ fromTime: (time: number | Date) => u32;
41
+ fromDuration: (duration: number) => u32;
42
+ };
43
+
44
+ export const createReplicationDomainTime = (
45
+ origin: Date,
46
+ unit: TimeUnit = "milliseconds",
47
+ ): ReplicationDomainTime => {
48
+ const originScaled = +origin * scalarMilliToUnit[unit];
49
+ const fromMilliToUnit = scalarMilliToUnit[unit];
50
+ const fromTime = (time: number | Date): u32 => {
51
+ return (
52
+ (typeof time === "number" ? time : +time * fromMilliToUnit) - originScaled
53
+ );
54
+ };
55
+
56
+ const fromDuration = (duration: number): u32 => {
57
+ return duration;
58
+ };
59
+ return {
60
+ type: "time",
61
+ fromTime,
62
+ fromDuration,
63
+ fromEntry: fromEntry(origin, unit),
64
+ fromArgs: async (args: TimeRange | undefined, log) => {
65
+ if (!args) {
66
+ return {
67
+ offset: log.node.identity.publicKey,
68
+ };
69
+ }
70
+ return {
71
+ offset: fromTime(args.from),
72
+ length: fromDuration(args.to - args.from),
73
+ };
74
+ /* roleAge = roleAge ?? (await log.getDefaultMinRoleAge());
75
+ const ranges = await getCoverSet(
76
+ log.replicationIndex,
77
+ roleAge,
78
+ fromTime(args.from),
79
+ fromDuration(args.to - args.from),
80
+ undefined,
81
+ );
82
+ return [...ranges]; */
83
+ },
84
+ };
85
+ };
@@ -0,0 +1,50 @@
1
+ import type { PublicSignKey } from "@peerbit/crypto";
2
+ import { type Index } from "@peerbit/indexer-interface";
3
+ import type { Entry, ShallowEntry } from "@peerbit/log";
4
+ import type {
5
+ ReplicationLimits,
6
+ ReplicationRangeIndexable,
7
+ } from "./replication.js";
8
+ import { MAX_U32 } from "./role.js";
9
+
10
+ export type u32 = number;
11
+ export type ReplicationDomainMapper<T> = (
12
+ entry: Entry<T> | ShallowEntry,
13
+ ) => Promise<u32> | u32;
14
+
15
+ export type Log = {
16
+ replicas: ReplicationLimits;
17
+ node: {
18
+ identity: {
19
+ publicKey: PublicSignKey;
20
+ };
21
+ };
22
+ syncInFlight: Map<string, Map<string, { timestamp: number }>>;
23
+ replicationIndex: Index<ReplicationRangeIndexable>;
24
+ getDefaultMinRoleAge: () => Promise<number>;
25
+ };
26
+ export type ReplicationDomainCoverSet<Args> = (
27
+ log: Log,
28
+ roleAge: number | undefined,
29
+ args: Args,
30
+ ) => Promise<string[]> | string[]; // minimum set of peers that covers all the data
31
+
32
+ type CoverRange = {
33
+ offset: number | PublicSignKey;
34
+ length?: number;
35
+ };
36
+ export type ReplicationDomain<Args, T> = {
37
+ type: string;
38
+ fromEntry: ReplicationDomainMapper<T>;
39
+ fromArgs: (
40
+ args: Args | undefined,
41
+ log: Log,
42
+ ) => Promise<CoverRange> | CoverRange;
43
+ };
44
+
45
+ export const uniformToU32 = (cursor: number) => {
46
+ return cursor * MAX_U32;
47
+ };
48
+
49
+ export type ExtractDomainArgs<T> =
50
+ T extends ReplicationDomain<infer Args, any> ? Args : never;
@@ -1,5 +1,4 @@
1
1
  import {
2
- BinaryReader,
3
2
  deserialize,
4
3
  field,
5
4
  option,
@@ -10,18 +9,13 @@ import {
10
9
  import { PublicSignKey, equals, randomBytes } from "@peerbit/crypto";
11
10
  import { type Index, id } from "@peerbit/indexer-interface";
12
11
  import { TransportMessage } from "./message.js";
13
- import {
14
- Observer,
15
- Replicator,
16
- Role,
17
- SEGMENT_COORDINATE_SCALE,
18
- } from "./role.js";
12
+ import { MAX_U32, Observer, Replicator, Role, scaleToU32 } from "./role.js";
19
13
 
20
14
  export type ReplicationLimits = { min: MinReplicas; max?: MinReplicas };
21
15
 
22
16
  export enum ReplicationIntent {
23
- Explicit = 0,
24
- Automatic = 1,
17
+ NonStrict = 0, // indicates that the segment will be replicated and nearby data might be replicated as well
18
+ Strict = 1, // only replicate data in the segment to the specified replicator, not any other data
25
19
  }
26
20
 
27
21
  export const getSegmentsFromOffsetAndRange = (
@@ -30,15 +24,16 @@ export const getSegmentsFromOffsetAndRange = (
30
24
  ): [[number, number], [number, number]] => {
31
25
  let start1 = offset;
32
26
  let end1Unscaled = offset + factor; // only add factor if it is not 1 to prevent numerical issues (like (0.9 + 1) % 1 => 0.8999999)
33
- let end1 = Math.min(end1Unscaled, 1);
27
+ let end1 = Math.min(end1Unscaled, MAX_U32);
34
28
  return [
35
29
  [start1, end1],
36
- end1Unscaled > 1
37
- ? [0, (factor !== 1 ? offset + factor : offset) % 1]
30
+ end1Unscaled > MAX_U32
31
+ ? [0, (factor !== MAX_U32 ? offset + factor : offset) % MAX_U32]
38
32
  : [start1, end1],
39
33
  ];
40
34
  };
41
35
 
36
+ @variant(0)
42
37
  export class ReplicationRange {
43
38
  @field({ type: Uint8Array })
44
39
  private id: Uint8Array;
@@ -52,25 +47,30 @@ export class ReplicationRange {
52
47
  @field({ type: "u32" })
53
48
  private _factor: number;
54
49
 
50
+ @field({ type: "u8" })
51
+ mode: ReplicationIntent;
52
+
55
53
  constructor(properties: {
56
54
  id: Uint8Array;
57
55
  offset: number;
58
56
  factor: number;
59
57
  timestamp: bigint;
58
+ mode: ReplicationIntent;
60
59
  }) {
61
- const { id, offset, factor, timestamp } = properties;
60
+ const { id, offset, factor, timestamp, mode } = properties;
62
61
  this.id = id;
63
- this._offset = Math.round(offset * SEGMENT_COORDINATE_SCALE);
64
- this._factor = Math.round(factor * SEGMENT_COORDINATE_SCALE);
62
+ this._offset = offset;
63
+ this._factor = factor;
65
64
  this.timestamp = timestamp;
65
+ this.mode = mode;
66
66
  }
67
67
 
68
68
  get factor(): number {
69
- return this._factor / SEGMENT_COORDINATE_SCALE;
69
+ return this._factor;
70
70
  }
71
71
 
72
72
  get offset(): number {
73
- return this._offset / SEGMENT_COORDINATE_SCALE;
73
+ return this._offset;
74
74
  }
75
75
 
76
76
  toReplicationRangeIndexable(key: PublicSignKey): ReplicationRangeIndexable {
@@ -80,6 +80,7 @@ export class ReplicationRange {
80
80
  offset: this.offset,
81
81
  length: this.factor,
82
82
  timestamp: this.timestamp,
83
+ mode: this.mode,
83
84
  });
84
85
  }
85
86
  }
@@ -110,15 +111,15 @@ export class ReplicationRangeIndexable {
110
111
  width: number;
111
112
 
112
113
  @field({ type: "u8" })
113
- replicationIntent: ReplicationIntent;
114
+ mode: ReplicationIntent;
114
115
 
115
116
  constructor(
116
117
  properties: {
117
118
  id?: Uint8Array;
118
-
119
+ normalized?: boolean;
119
120
  offset: number;
120
121
  length: number;
121
- replicationIntent?: ReplicationIntent;
122
+ mode?: ReplicationIntent;
122
123
  timestamp?: bigint;
123
124
  } & ({ publicKeyHash: string } | { publicKey: PublicSignKey }),
124
125
  ) {
@@ -126,9 +127,16 @@ export class ReplicationRangeIndexable {
126
127
  this.hash =
127
128
  (properties as { publicKeyHash: string }).publicKeyHash ||
128
129
  (properties as { publicKey: PublicSignKey }).publicKey.hashcode();
129
- this.transform({ length: properties.length, offset: properties.offset });
130
- this.replicationIntent =
131
- properties.replicationIntent ?? ReplicationIntent.Explicit;
130
+ if (!properties.normalized) {
131
+ this.transform({ length: properties.length, offset: properties.offset });
132
+ } else {
133
+ this.transform({
134
+ length: scaleToU32(properties.length),
135
+ offset: scaleToU32(properties.offset),
136
+ });
137
+ }
138
+
139
+ this.mode = properties.mode ?? ReplicationIntent.NonStrict;
132
140
  this.timestamp = properties.timestamp || BigInt(0);
133
141
  }
134
142
 
@@ -137,10 +145,10 @@ export class ReplicationRangeIndexable {
137
145
  properties.offset,
138
146
  properties.length,
139
147
  );
140
- this.start1 = Math.round(ranges[0][0] * SEGMENT_COORDINATE_SCALE);
141
- this.end1 = Math.round(ranges[0][1] * SEGMENT_COORDINATE_SCALE);
142
- this.start2 = Math.round(ranges[1][0] * SEGMENT_COORDINATE_SCALE);
143
- this.end2 = Math.round(ranges[1][1] * SEGMENT_COORDINATE_SCALE);
148
+ this.start1 = Math.round(ranges[0][0]);
149
+ this.end1 = Math.round(ranges[0][1]);
150
+ this.start2 = Math.round(ranges[1][0]);
151
+ this.end2 = Math.round(ranges[1][1]);
144
152
 
145
153
  this.width =
146
154
  this.end1 -
@@ -152,7 +160,8 @@ export class ReplicationRangeIndexable {
152
160
  this.end1 > 0xffffffff ||
153
161
  this.start2 > 0xffffffff ||
154
162
  this.end2 > 0xffffffff ||
155
- this.width > 0xffffffff
163
+ this.width > 0xffffffff ||
164
+ this.width < 0
156
165
  ) {
157
166
  throw new Error("Segment coordinate out of bounds");
158
167
  }
@@ -183,14 +192,15 @@ export class ReplicationRangeIndexable {
183
192
  toReplicationRange() {
184
193
  return new ReplicationRange({
185
194
  id: this.id,
186
- offset: this.start1 / SEGMENT_COORDINATE_SCALE,
187
- factor: this.width / SEGMENT_COORDINATE_SCALE,
195
+ offset: this.start1,
196
+ factor: this.width,
188
197
  timestamp: this.timestamp,
198
+ mode: this.mode,
189
199
  });
190
200
  }
191
201
 
192
202
  distanceTo(point: number) {
193
- let wrappedPoint = SEGMENT_COORDINATE_SCALE - point;
203
+ let wrappedPoint = MAX_U32 - point;
194
204
  return Math.min(
195
205
  Math.abs(this.start1 - point),
196
206
  Math.abs(this.end2 - point),
@@ -203,7 +213,7 @@ export class ReplicationRangeIndexable {
203
213
  }
204
214
 
205
215
  get widthNormalized() {
206
- return this.width / SEGMENT_COORDINATE_SCALE;
216
+ return this.width / MAX_U32;
207
217
  }
208
218
 
209
219
  equals(other: ReplicationRangeIndexable) {
@@ -211,7 +221,7 @@ export class ReplicationRangeIndexable {
211
221
  equals(this.id, other.id) &&
212
222
  this.hash === other.hash &&
213
223
  this.timestamp === other.timestamp &&
214
- this.replicationIntent === other.replicationIntent &&
224
+ this.mode === other.mode &&
215
225
  this.start1 === other.start1 &&
216
226
  this.end1 === other.end1 &&
217
227
  this.start2 === other.start2 &&
@@ -228,9 +238,9 @@ export class ReplicationRangeIndexable {
228
238
  let roundToTwoDecimals = (num: number) => Math.round(num * 100) / 100;
229
239
 
230
240
  if (Math.abs(this.start1 - this.start2) < 0.0001) {
231
- return `([${roundToTwoDecimals(this.start1 / SEGMENT_COORDINATE_SCALE)}, ${roundToTwoDecimals(this.end1 / SEGMENT_COORDINATE_SCALE)}])`;
241
+ return `([${roundToTwoDecimals(this.start1 / MAX_U32)}, ${roundToTwoDecimals(this.end1 / MAX_U32)}])`;
232
242
  }
233
- return `([${roundToTwoDecimals(this.start1 / SEGMENT_COORDINATE_SCALE)}, ${roundToTwoDecimals(this.end1 / SEGMENT_COORDINATE_SCALE)}] [${roundToTwoDecimals(this.start2 / SEGMENT_COORDINATE_SCALE)}, ${roundToTwoDecimals(this.end2 / SEGMENT_COORDINATE_SCALE)}])`;
243
+ return `([${roundToTwoDecimals(this.start1 / MAX_U32)}, ${roundToTwoDecimals(this.end1 / MAX_U32)}] [${roundToTwoDecimals(this.start2 / MAX_U32)}, ${roundToTwoDecimals(this.end2 / MAX_U32)}])`;
234
244
  }
235
245
  }
236
246
 
@@ -278,8 +288,8 @@ export class ResponseRoleMessage extends TransportMessage {
278
288
  this.role = properties.role;
279
289
  }
280
290
 
281
- toReplicationInfoMessage(): ResponseReplicationInfoMessage {
282
- return new ResponseReplicationInfoMessage({
291
+ toReplicationInfoMessage(): AllReplicatingSegmentsMessage {
292
+ return new AllReplicatingSegmentsMessage({
283
293
  segments:
284
294
  this.role instanceof Replicator
285
295
  ? this.role.segments.map((x) => {
@@ -288,6 +298,7 @@ export class ResponseRoleMessage extends TransportMessage {
288
298
  offset: x.offset,
289
299
  factor: x.factor,
290
300
  timestamp: x.timestamp,
301
+ mode: ReplicationIntent.NonStrict,
291
302
  });
292
303
  })
293
304
  : [],
@@ -296,7 +307,7 @@ export class ResponseRoleMessage extends TransportMessage {
296
307
  }
297
308
 
298
309
  @variant([1, 2])
299
- export class ResponseReplicationInfoMessage extends TransportMessage {
310
+ export class AllReplicatingSegmentsMessage extends TransportMessage {
300
311
  @field({ type: vec(ReplicationRange) })
301
312
  segments: ReplicationRange[];
302
313
 
@@ -307,7 +318,7 @@ export class ResponseReplicationInfoMessage extends TransportMessage {
307
318
  }
308
319
 
309
320
  @variant([1, 3])
310
- export class StartedReplicating extends TransportMessage {
321
+ export class AddedReplicationSegmentMessage extends TransportMessage {
311
322
  @field({ type: vec(ReplicationRange) })
312
323
  segments: ReplicationRange[];
313
324
 
@@ -376,10 +387,3 @@ export const maxReplicas = (
376
387
  const numberOfLeaders = Math.max(Math.min(higher, max), lower);
377
388
  return numberOfLeaders;
378
389
  };
379
-
380
- export const hashToUniformNumber = (hash: Uint8Array) => {
381
- const seedNumber = new BinaryReader(
382
- hash.subarray(hash.length - 4, hash.length),
383
- ).u32();
384
- return seedNumber / 0xffffffff; // bounded between 0 and 1
385
- };
package/src/role.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  */
6
6
  import { field, variant, vec } from "@dao-xyz/borsh";
7
7
 
8
- export const SEGMENT_COORDINATE_SCALE = 4294967295;
8
+ export const MAX_U32 = 4294967295;
9
+ export const scaleToU32 = (value: number) => Math.round(MAX_U32 * value);
9
10
 
10
11
  export const overlaps = (x1: number, x2: number, y1: number, y2: number) => {
11
12
  if (x1 <= y2 && y1 <= x2) {
@@ -59,20 +60,20 @@ export class RoleReplicationSegment {
59
60
  }
60
61
 
61
62
  this.timestamp = timestamp ?? BigInt(+new Date());
62
- this.factorNominator = Math.round(SEGMENT_COORDINATE_SCALE * factor);
63
+ this.factorNominator = Math.round(MAX_U32 * factor);
63
64
 
64
65
  if (offset > 1 || offset < 0) {
65
66
  throw new Error("Expecting offset to be between 0 and 1, got: " + offset);
66
67
  }
67
- this.offsetNominator = Math.round(SEGMENT_COORDINATE_SCALE * offset);
68
+ this.offsetNominator = Math.round(MAX_U32 * offset);
68
69
  }
69
70
 
70
71
  get factor(): number {
71
- return this.factorNominator / SEGMENT_COORDINATE_SCALE;
72
+ return this.factorNominator / MAX_U32;
72
73
  }
73
74
 
74
75
  get offset(): number {
75
- return this.offsetNominator / SEGMENT_COORDINATE_SCALE;
76
+ return this.offsetNominator / MAX_U32;
76
77
  }
77
78
  }
78
79