@aztec/kv-store 3.0.0-canary.a9708bd → 3.0.0-devnet.20251212

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 (94) hide show
  1. package/dest/config.d.ts +2 -2
  2. package/dest/config.js +2 -2
  3. package/dest/indexeddb/array.d.ts +1 -1
  4. package/dest/indexeddb/array.d.ts.map +1 -1
  5. package/dest/indexeddb/index.d.ts +1 -1
  6. package/dest/indexeddb/index.js +1 -1
  7. package/dest/indexeddb/map.d.ts +1 -1
  8. package/dest/indexeddb/map.d.ts.map +1 -1
  9. package/dest/indexeddb/multi_map.d.ts +2 -1
  10. package/dest/indexeddb/multi_map.d.ts.map +1 -1
  11. package/dest/indexeddb/multi_map.js +14 -0
  12. package/dest/indexeddb/set.d.ts +1 -1
  13. package/dest/indexeddb/set.d.ts.map +1 -1
  14. package/dest/indexeddb/singleton.d.ts +1 -1
  15. package/dest/indexeddb/singleton.d.ts.map +1 -1
  16. package/dest/indexeddb/store.d.ts +1 -1
  17. package/dest/indexeddb/store.d.ts.map +1 -1
  18. package/dest/interfaces/array.d.ts +1 -1
  19. package/dest/interfaces/array_test_suite.d.ts +1 -1
  20. package/dest/interfaces/common.d.ts +1 -1
  21. package/dest/interfaces/counter.d.ts +1 -1
  22. package/dest/interfaces/index.d.ts +1 -1
  23. package/dest/interfaces/map.d.ts +1 -1
  24. package/dest/interfaces/map_test_suite.d.ts +1 -1
  25. package/dest/interfaces/multi_map.d.ts +7 -1
  26. package/dest/interfaces/multi_map.d.ts.map +1 -1
  27. package/dest/interfaces/multi_map_test_suite.d.ts +1 -1
  28. package/dest/interfaces/multi_map_test_suite.d.ts.map +1 -1
  29. package/dest/interfaces/multi_map_test_suite.js +94 -0
  30. package/dest/interfaces/set.d.ts +1 -1
  31. package/dest/interfaces/set_test_suite.d.ts +1 -1
  32. package/dest/interfaces/singleton.d.ts +1 -1
  33. package/dest/interfaces/singleton_test_suite.d.ts +1 -1
  34. package/dest/interfaces/store.d.ts +1 -1
  35. package/dest/interfaces/utils.d.ts +1 -1
  36. package/dest/interfaces/utils.d.ts.map +1 -1
  37. package/dest/lmdb/array.d.ts +1 -1
  38. package/dest/lmdb/array.d.ts.map +1 -1
  39. package/dest/lmdb/counter.d.ts +1 -1
  40. package/dest/lmdb/counter.d.ts.map +1 -1
  41. package/dest/lmdb/index.d.ts +1 -1
  42. package/dest/lmdb/index.js +2 -2
  43. package/dest/lmdb/map.d.ts +1 -1
  44. package/dest/lmdb/map.d.ts.map +1 -1
  45. package/dest/lmdb/multi_map.d.ts +2 -1
  46. package/dest/lmdb/multi_map.d.ts.map +1 -1
  47. package/dest/lmdb/multi_map.js +15 -0
  48. package/dest/lmdb/set.d.ts +1 -1
  49. package/dest/lmdb/set.d.ts.map +1 -1
  50. package/dest/lmdb/singleton.d.ts +1 -1
  51. package/dest/lmdb/singleton.d.ts.map +1 -1
  52. package/dest/lmdb/store.d.ts +2 -3
  53. package/dest/lmdb/store.d.ts.map +1 -1
  54. package/dest/lmdb/store.js +1 -1
  55. package/dest/lmdb-v2/array.d.ts +1 -1
  56. package/dest/lmdb-v2/array.d.ts.map +1 -1
  57. package/dest/lmdb-v2/factory.d.ts +4 -7
  58. package/dest/lmdb-v2/factory.d.ts.map +1 -1
  59. package/dest/lmdb-v2/factory.js +3 -3
  60. package/dest/lmdb-v2/index.d.ts +1 -1
  61. package/dest/lmdb-v2/map.d.ts +1 -1
  62. package/dest/lmdb-v2/map.d.ts.map +1 -1
  63. package/dest/lmdb-v2/message.d.ts +1 -1
  64. package/dest/lmdb-v2/multi_map.d.ts +2 -1
  65. package/dest/lmdb-v2/multi_map.d.ts.map +1 -1
  66. package/dest/lmdb-v2/multi_map.js +11 -0
  67. package/dest/lmdb-v2/read_transaction.d.ts +1 -1
  68. package/dest/lmdb-v2/read_transaction.d.ts.map +1 -1
  69. package/dest/lmdb-v2/set.d.ts +1 -1
  70. package/dest/lmdb-v2/set.d.ts.map +1 -1
  71. package/dest/lmdb-v2/singleton.d.ts +1 -1
  72. package/dest/lmdb-v2/singleton.d.ts.map +1 -1
  73. package/dest/lmdb-v2/store.d.ts +1 -1
  74. package/dest/lmdb-v2/store.d.ts.map +1 -1
  75. package/dest/lmdb-v2/utils.d.ts +1 -1
  76. package/dest/lmdb-v2/write_transaction.d.ts +1 -1
  77. package/dest/lmdb-v2/write_transaction.d.ts.map +1 -1
  78. package/dest/stores/index.d.ts +1 -1
  79. package/dest/stores/l2_tips_store.d.ts +3 -2
  80. package/dest/stores/l2_tips_store.d.ts.map +1 -1
  81. package/dest/stores/l2_tips_store.js +5 -3
  82. package/dest/utils.d.ts +1 -1
  83. package/package.json +10 -7
  84. package/src/config.ts +3 -3
  85. package/src/indexeddb/index.ts +2 -2
  86. package/src/indexeddb/multi_map.ts +12 -0
  87. package/src/interfaces/multi_map.ts +7 -0
  88. package/src/interfaces/multi_map_test_suite.ts +100 -0
  89. package/src/lmdb/index.ts +3 -3
  90. package/src/lmdb/multi_map.ts +16 -0
  91. package/src/lmdb/store.ts +1 -1
  92. package/src/lmdb-v2/factory.ts +3 -3
  93. package/src/lmdb-v2/multi_map.ts +9 -0
  94. package/src/stores/l2_tips_store.ts +7 -5
@@ -61,6 +61,13 @@ export function describeAztecMultiMap(
61
61
  : await toArray((multiMap as AztecAsyncMultiMap<any, any>).getValuesAsync(key));
62
62
  }
63
63
 
64
+ async function getValueCount(
65
+ key: Key,
66
+ sut: AztecAsyncMultiMap<any, any> | AztecMultiMap<any, any> = multiMap,
67
+ ): Promise<number> {
68
+ return await (sut as AztecAsyncMultiMap<any, any>).getValueCountAsync(key);
69
+ }
70
+
64
71
  it('should be able to set and get values', async () => {
65
72
  await multiMap.set('foo', 'bar');
66
73
  await multiMap.set('baz', 'qux');
@@ -238,5 +245,98 @@ export function describeAztecMultiMap(
238
245
  expect(await keys({ start: 'b', reverse: true })).to.deep.equal(['d', 'c']);
239
246
  expect(await keys({ end: 'b', reverse: true })).to.deep.equal(['b', 'a']);
240
247
  });
248
+
249
+ it('returns 0 for missing key', async () => {
250
+ expect(await getValueCount('missing')).to.equal(0);
251
+ });
252
+
253
+ it('counts a single value', async () => {
254
+ await multiMap.set('foo', 'bar');
255
+ expect(await getValueCount('foo')).to.equal(1);
256
+ });
257
+
258
+ it('counts multiple distinct values for same key', async () => {
259
+ await multiMap.set('foo', 'bar');
260
+ await multiMap.set('foo', 'baz');
261
+ await multiMap.set('foo', 'qux');
262
+ expect(await getValueCount('foo')).to.equal(3);
263
+ });
264
+
265
+ it('does not increase count for duplicate inserts', async () => {
266
+ await multiMap.set('foo', 'bar');
267
+ await multiMap.set('foo', 'bar');
268
+ await multiMap.set('foo', 'baz');
269
+ await multiMap.set('foo', 'baz');
270
+ expect(await getValueCount('foo')).to.equal(2);
271
+ expect(await getValues('foo')).to.have.members(['bar', 'baz']);
272
+ });
273
+
274
+ it('decrements when deleting a single value', async () => {
275
+ await multiMap.set('foo', '1');
276
+ await multiMap.set('foo', '2');
277
+ await multiMap.set('foo', '3');
278
+ expect(await getValueCount('foo')).to.equal(3);
279
+ await multiMap.deleteValue('foo', '2');
280
+ expect(await getValueCount('foo')).to.equal(2);
281
+ expect(await getValues('foo')).to.have.members(['1', '3']);
282
+ });
283
+
284
+ it('does not change count when deleting a non-existent value', async () => {
285
+ await multiMap.set('foo', '1');
286
+ await multiMap.set('foo', '3');
287
+ expect(await getValueCount('foo')).to.equal(2);
288
+ await multiMap.deleteValue('foo', '2');
289
+ expect(await getValueCount('foo')).to.equal(2);
290
+ });
291
+
292
+ it('clears all values when deleting a key', async () => {
293
+ await multiMap.set('foo', 'bar');
294
+ await multiMap.set('foo', 'baz');
295
+ expect(await getValueCount('foo')).to.equal(2);
296
+ await multiMap.delete('foo');
297
+ expect(await getValueCount('foo')).to.equal(0);
298
+ expect(await getValues('foo')).to.deep.equal([]);
299
+ });
300
+
301
+ it('count equals enumerated values length', async () => {
302
+ await multiMap.set('foo', '1');
303
+ await multiMap.set('foo', '2');
304
+ const vals = await getValues('foo');
305
+ expect(await getValueCount('foo')).to.equal(vals.length);
306
+ });
307
+
308
+ it('sum of per-key counts equals total size', async () => {
309
+ await multiMap.set('foo', '1');
310
+ await multiMap.set('foo', '2');
311
+ await multiMap.set('bar', '3');
312
+ await multiMap.set('bar', '4');
313
+ await multiMap.set('baz', '5');
314
+
315
+ const allKeys = await keys();
316
+ const uniqueKeys = Array.from(new Set(allKeys));
317
+ const counts = await Promise.all(uniqueKeys.map(k => getValueCount(k)));
318
+ const sum = counts.reduce((s, n) => s + n, 0);
319
+ expect(sum).to.equal(await size());
320
+ });
321
+
322
+ it('supports sparse slots: delete middle, reinsert new, count remains correct', async () => {
323
+ await multiMap.set('foo', '1');
324
+ await multiMap.set('foo', '2');
325
+ await multiMap.set('foo', '3');
326
+ expect(await getValueCount('foo')).to.equal(3);
327
+ await multiMap.deleteValue('foo', '2');
328
+ expect(await getValueCount('foo')).to.equal(2);
329
+ await multiMap.set('foo', '4');
330
+ expect(await getValueCount('foo')).to.equal(3);
331
+ expect(await getValues('foo')).to.have.members(['1', '3', '4']);
332
+ });
333
+
334
+ it('multiple keys are independent', async () => {
335
+ await multiMap.set('foo', '1');
336
+ await multiMap.set('foo', '2');
337
+ await multiMap.set('bar', '3');
338
+ expect(await getValueCount('foo')).to.equal(2);
339
+ expect(await getValueCount('bar')).to.equal(1);
340
+ });
241
341
  });
242
342
  }
package/src/lmdb/index.ts CHANGED
@@ -16,11 +16,11 @@ export function createStore(name: string, config: DataStoreConfig, log: Logger =
16
16
 
17
17
  log.info(
18
18
  dataDirectory
19
- ? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.dataStoreMapSizeKB} KB`
20
- : `Creating ${name} ephemeral data store with map size ${config.dataStoreMapSizeKB} KB`,
19
+ ? `Creating ${name} data store at directory ${dataDirectory} with map size ${config.dataStoreMapSizeKb} KB`
20
+ : `Creating ${name} ephemeral data store with map size ${config.dataStoreMapSizeKb} KB`,
21
21
  );
22
22
 
23
- const store = AztecLmdbStore.open(dataDirectory, config.dataStoreMapSizeKB, false);
23
+ const store = AztecLmdbStore.open(dataDirectory, config.dataStoreMapSizeKb, false);
24
24
  if (config.l1Contracts?.rollupAddress) {
25
25
  return initStoreForRollup(store, config.l1Contracts.rollupAddress, log);
26
26
  }
@@ -29,6 +29,22 @@ export class LmdbAztecMultiMap<K extends Key, V extends Value>
29
29
  }
30
30
  }
31
31
 
32
+ getValueCountAsync(key: K): Promise<number> {
33
+ const transaction = this.db.useReadTransaction();
34
+ try {
35
+ const values = this.db.getValues(this.slot(key), {
36
+ transaction,
37
+ });
38
+ let count = 0;
39
+ for (const _ of values) {
40
+ count++;
41
+ }
42
+ return Promise.resolve(count);
43
+ } finally {
44
+ transaction.done();
45
+ }
46
+ }
47
+
32
48
  async deleteValue(key: K, val: V): Promise<void> {
33
49
  await this.db.remove(this.slot(key), [key, val]);
34
50
  }
package/src/lmdb/store.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { randomBytes } from '@aztec/foundation/crypto';
1
+ import { randomBytes } from '@aztec/foundation/crypto/random';
2
2
  import { createLogger } from '@aztec/foundation/log';
3
3
 
4
4
  import { promises as fs, mkdirSync } from 'fs';
@@ -33,15 +33,15 @@ export async function createStore(
33
33
  rollupAddress,
34
34
  dataDirectory: subDir,
35
35
  onOpen: dbDirectory =>
36
- AztecLMDBStoreV2.new(dbDirectory, config.dataStoreMapSizeKB, MAX_READERS, () => Promise.resolve(), log),
36
+ AztecLMDBStoreV2.new(dbDirectory, config.dataStoreMapSizeKb, MAX_READERS, () => Promise.resolve(), log),
37
37
  });
38
38
 
39
39
  log.info(
40
- `Creating ${name} data store at directory ${subDir} with map size ${config.dataStoreMapSizeKB} KB (LMDB v2)`,
40
+ `Creating ${name} data store at directory ${subDir} with map size ${config.dataStoreMapSizeKb} KB (LMDB v2)`,
41
41
  );
42
42
  [store] = await versionManager.open();
43
43
  } else {
44
- store = await openTmpStore(name, true, config.dataStoreMapSizeKB, MAX_READERS, log);
44
+ store = await openTmpStore(name, true, config.dataStoreMapSizeKb, MAX_READERS, log);
45
45
  }
46
46
 
47
47
  return store;
@@ -1,8 +1,10 @@
1
1
  import { Encoder } from 'msgpackr/pack';
2
+ import { MAXIMUM_KEY, toBufferKey } from 'ordered-binary';
2
3
 
3
4
  import type { Key, Range, Value } from '../interfaces/common.js';
4
5
  import type { AztecAsyncMultiMap } from '../interfaces/multi_map.js';
5
6
  import type { ReadTransaction } from './read_transaction.js';
7
+ // eslint-disable-next-line import/no-cycle
6
8
  import { type AztecLMDBStoreV2, execInReadTx, execInWriteTx } from './store.js';
7
9
  import { deserializeKey, maxKey, minKey, serializeKey } from './utils.js';
8
10
 
@@ -138,4 +140,11 @@ export class LMDBMultiMap<K extends Key, V extends Value> implements AztecAsyncM
138
140
  yield this.encoder.unpack(value);
139
141
  }
140
142
  }
143
+
144
+ getValueCountAsync(key: K): Promise<number> {
145
+ const startKey = serializeKey(this.prefix, key);
146
+ const endKey = toBufferKey([this.prefix, key, MAXIMUM_KEY]);
147
+
148
+ return execInReadTx(this.store, tx => tx.countEntriesIndex(startKey, endKey, false));
149
+ }
141
150
  }
@@ -1,3 +1,5 @@
1
+ import { GENESIS_BLOCK_HEADER_HASH } from '@aztec/constants';
2
+ import { BlockNumber } from '@aztec/foundation/branded-types';
1
3
  import type {
2
4
  L2BlockId,
3
5
  L2BlockStreamEvent,
@@ -12,15 +14,15 @@ import type { AztecAsyncKVStore } from '../interfaces/store.js';
12
14
 
13
15
  /** Stores currently synced L2 tips and unfinalized block hashes. */
14
16
  export class L2TipsKVStore implements L2BlockStreamEventHandler, L2BlockStreamLocalDataProvider {
15
- private readonly l2TipsStore: AztecAsyncMap<L2BlockTag, number>;
16
- private readonly l2BlockHashesStore: AztecAsyncMap<number, string>;
17
+ private readonly l2TipsStore: AztecAsyncMap<L2BlockTag, BlockNumber>;
18
+ private readonly l2BlockHashesStore: AztecAsyncMap<BlockNumber, string>;
17
19
 
18
20
  constructor(store: AztecAsyncKVStore, namespace: string) {
19
21
  this.l2TipsStore = store.openMap([namespace, 'l2_tips'].join('_'));
20
22
  this.l2BlockHashesStore = store.openMap([namespace, 'l2_block_hashes'].join('_'));
21
23
  }
22
24
 
23
- public getL2BlockHash(number: number): Promise<string | undefined> {
25
+ public getL2BlockHash(number: BlockNumber): Promise<string | undefined> {
24
26
  return this.l2BlockHashesStore.getAsync(number);
25
27
  }
26
28
 
@@ -35,7 +37,7 @@ export class L2TipsKVStore implements L2BlockStreamEventHandler, L2BlockStreamLo
35
37
  private async getL2Tip(tag: L2BlockTag): Promise<L2BlockId> {
36
38
  const blockNumber = await this.l2TipsStore.getAsync(tag);
37
39
  if (blockNumber === undefined || blockNumber === 0) {
38
- return { number: 0, hash: undefined };
40
+ return { number: BlockNumber.ZERO, hash: GENESIS_BLOCK_HEADER_HASH.toString() };
39
41
  }
40
42
  const blockHash = await this.l2BlockHashesStore.getAsync(blockNumber);
41
43
  if (!blockHash) {
@@ -50,7 +52,7 @@ export class L2TipsKVStore implements L2BlockStreamEventHandler, L2BlockStreamLo
50
52
  case 'blocks-added': {
51
53
  const blocks = event.blocks.map(b => b.block);
52
54
  for (const block of blocks) {
53
- await this.l2BlockHashesStore.set(block.number, (await block.header.hash()).toString());
55
+ await this.l2BlockHashesStore.set(block.number, (await block.hash()).toString());
54
56
  }
55
57
  await this.l2TipsStore.set('latest', blocks.at(-1)!.number);
56
58
  break;