|
1 | 1 | import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store'; |
2 | 2 | import type { DirectionalAppTaggingSecret } from '@aztec/stdlib/logs'; |
3 | 3 |
|
| 4 | +import type { StagedStore } from '../../job_coordinator/job_coordinator.js'; |
| 5 | + |
4 | 6 | /** |
5 | 7 | * Data provider of tagging data used when syncing the logs as a recipient. The sender counterpart of this class |
6 | 8 | * is called SenderTaggingStore. We have the providers separate for the sender and recipient because |
7 | 9 | * the algorithms are completely disjoint and there is not data reuse between the two. |
8 | 10 | * |
9 | 11 | * @dev Chain reorgs do not need to be handled here because both the finalized and aged indexes refer to finalized |
10 | 12 | * blocks, which by definition cannot be affected by reorgs. |
11 | | - * |
12 | | - * TODO(benesjan): Relocate to yarn-project/pxe/src/storage/tagging_store |
13 | 13 | */ |
14 | | -export class RecipientTaggingStore { |
| 14 | +export class RecipientTaggingStore implements StagedStore { |
| 15 | + storeName: string = 'recipient_tagging'; |
| 16 | + |
15 | 17 | #store: AztecAsyncKVStore; |
16 | 18 |
|
17 | 19 | #highestAgedIndex: AztecAsyncMap<string, number>; |
18 | 20 | #highestFinalizedIndex: AztecAsyncMap<string, number>; |
19 | 21 |
|
| 22 | + // jobId => secret => number |
| 23 | + #highestAgedIndexForJob: Map<string, Map<string, number>>; |
| 24 | + |
| 25 | + // jobId => secret => number |
| 26 | + #highestFinalizedIndexForJob: Map<string, Map<string, number>>; |
| 27 | + |
20 | 28 | constructor(store: AztecAsyncKVStore) { |
21 | 29 | this.#store = store; |
22 | 30 |
|
23 | 31 | this.#highestAgedIndex = this.#store.openMap('highest_aged_index'); |
24 | 32 | this.#highestFinalizedIndex = this.#store.openMap('highest_finalized_index'); |
| 33 | + |
| 34 | + this.#highestAgedIndexForJob = new Map(); |
| 35 | + this.#highestFinalizedIndexForJob = new Map(); |
| 36 | + } |
| 37 | + |
| 38 | + #getHighestAgedIndexForJob(jobId: string): Map<string, number> { |
| 39 | + let highestAgedIndexForJob = this.#highestAgedIndexForJob.get(jobId); |
| 40 | + if (!highestAgedIndexForJob) { |
| 41 | + highestAgedIndexForJob = new Map(); |
| 42 | + this.#highestAgedIndexForJob.set(jobId, highestAgedIndexForJob); |
| 43 | + } |
| 44 | + return highestAgedIndexForJob; |
| 45 | + } |
| 46 | + |
| 47 | + async #readHighestAgedIndex(jobId: string, secret: string): Promise<number | undefined> { |
| 48 | + return this.#getHighestAgedIndexForJob(jobId).get(secret) ?? (await this.#highestAgedIndex.getAsync(secret)); |
| 49 | + } |
| 50 | + |
| 51 | + #writeHighestAgedIndex(jobId: string, secret: string, index: number) { |
| 52 | + this.#getHighestAgedIndexForJob(jobId).set(secret, index); |
| 53 | + } |
| 54 | + |
| 55 | + #getHighestFinalizedIndexForJob(jobId: string): Map<string, number> { |
| 56 | + let jobStagedHighestFinalizedIndex = this.#highestFinalizedIndexForJob.get(jobId); |
| 57 | + if (!jobStagedHighestFinalizedIndex) { |
| 58 | + jobStagedHighestFinalizedIndex = new Map(); |
| 59 | + this.#highestFinalizedIndexForJob.set(jobId, jobStagedHighestFinalizedIndex); |
| 60 | + } |
| 61 | + return jobStagedHighestFinalizedIndex; |
| 62 | + } |
| 63 | + |
| 64 | + async #readHighestFinalizedIndex(jobId: string, secret: string): Promise<number | undefined> { |
| 65 | + return ( |
| 66 | + this.#getHighestFinalizedIndexForJob(jobId).get(secret) ?? (await this.#highestFinalizedIndex.getAsync(secret)) |
| 67 | + ); |
| 68 | + } |
| 69 | + |
| 70 | + #writeHighestFinalizedIndex(jobId: string, secret: string, index: number) { |
| 71 | + this.#getHighestFinalizedIndexForJob(jobId).set(secret, index); |
| 72 | + } |
| 73 | + |
| 74 | + /** |
| 75 | + * Writes all job-specific in-memory data to persistent storage. |
| 76 | + * |
| 77 | + * @remark This method must run in a DB transaction context. It's designed to be called from JobCoordinator#commitJob. |
| 78 | + */ |
| 79 | + async commit(jobId: string): Promise<void> { |
| 80 | + const highestAgedIndexForJob = this.#highestAgedIndexForJob.get(jobId); |
| 81 | + if (highestAgedIndexForJob) { |
| 82 | + for (const [secret, index] of highestAgedIndexForJob.entries()) { |
| 83 | + await this.#highestAgedIndex.set(secret, index); |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + const highestFinalizedIndexForJob = this.#highestFinalizedIndexForJob.get(jobId); |
| 88 | + if (highestFinalizedIndexForJob) { |
| 89 | + for (const [secret, index] of highestFinalizedIndexForJob.entries()) { |
| 90 | + await this.#highestFinalizedIndex.set(secret, index); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + return this.discardStaged(jobId); |
| 95 | + } |
| 96 | + |
| 97 | + discardStaged(jobId: string): Promise<void> { |
| 98 | + this.#highestAgedIndexForJob.delete(jobId); |
| 99 | + this.#highestFinalizedIndexForJob.delete(jobId); |
| 100 | + return Promise.resolve(); |
25 | 101 | } |
26 | 102 |
|
27 | | - getHighestAgedIndex(secret: DirectionalAppTaggingSecret): Promise<number | undefined> { |
28 | | - return this.#highestAgedIndex.getAsync(secret.toString()); |
| 103 | + getHighestAgedIndex(secret: DirectionalAppTaggingSecret, jobId: string): Promise<number | undefined> { |
| 104 | + return this.#readHighestAgedIndex(jobId, secret.toString()); |
29 | 105 | } |
30 | 106 |
|
31 | | - async updateHighestAgedIndex(secret: DirectionalAppTaggingSecret, index: number): Promise<void> { |
32 | | - const currentIndex = await this.#highestAgedIndex.getAsync(secret.toString()); |
| 107 | + async updateHighestAgedIndex(secret: DirectionalAppTaggingSecret, index: number, jobId: string): Promise<void> { |
| 108 | + const currentIndex = await this.#readHighestAgedIndex(jobId, secret.toString()); |
33 | 109 | if (currentIndex !== undefined && index <= currentIndex) { |
34 | 110 | // Log sync should never set a lower highest aged index. |
35 | 111 | throw new Error(`New highest aged index (${index}) must be higher than the current one (${currentIndex})`); |
36 | 112 | } |
37 | | - await this.#highestAgedIndex.set(secret.toString(), index); |
| 113 | + this.#writeHighestAgedIndex(jobId, secret.toString(), index); |
38 | 114 | } |
39 | 115 |
|
40 | | - getHighestFinalizedIndex(secret: DirectionalAppTaggingSecret): Promise<number | undefined> { |
41 | | - return this.#highestFinalizedIndex.getAsync(secret.toString()); |
| 116 | + getHighestFinalizedIndex(secret: DirectionalAppTaggingSecret, jobId: string): Promise<number | undefined> { |
| 117 | + return this.#readHighestFinalizedIndex(jobId, secret.toString()); |
42 | 118 | } |
43 | 119 |
|
44 | | - async updateHighestFinalizedIndex(secret: DirectionalAppTaggingSecret, index: number): Promise<void> { |
45 | | - const currentIndex = await this.#highestFinalizedIndex.getAsync(secret.toString()); |
| 120 | + async updateHighestFinalizedIndex(secret: DirectionalAppTaggingSecret, index: number, jobId: string): Promise<void> { |
| 121 | + const currentIndex = await this.#readHighestFinalizedIndex(jobId, secret.toString()); |
46 | 122 | if (currentIndex !== undefined && index < currentIndex) { |
47 | 123 | // Log sync should never set a lower highest finalized index but it can happen that it would try to set the same |
48 | 124 | // one because we are loading logs from highest aged index + 1 and not from the highest finalized index. |
49 | 125 | throw new Error(`New highest finalized index (${index}) must be higher than the current one (${currentIndex})`); |
50 | 126 | } |
51 | | - await this.#highestFinalizedIndex.set(secret.toString(), index); |
| 127 | + this.#writeHighestFinalizedIndex(jobId, secret.toString(), index); |
52 | 128 | } |
53 | 129 | } |
0 commit comments