@aztec/archiver 0.0.1-commit.993d240 → 0.0.1-commit.9a89641
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/dest/archiver.d.ts +44 -8
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +103 -35
- package/dest/config.d.ts +5 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +28 -7
- package/dest/factory.d.ts +8 -1
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +41 -5
- package/dest/l1/calldata_retriever.d.ts +8 -1
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +7 -6
- package/dest/l1/data_retrieval.d.ts +7 -2
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/modules/contract_data_source_adapter.d.ts +2 -2
- package/dest/modules/contract_data_source_adapter.d.ts.map +1 -1
- package/dest/modules/contract_data_source_adapter.js +1 -9
- package/dest/modules/data_source_base.d.ts +5 -6
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +9 -38
- package/dest/modules/data_store_updater.d.ts +1 -1
- package/dest/modules/data_store_updater.d.ts.map +1 -1
- package/dest/modules/data_store_updater.js +4 -3
- package/dest/modules/instrumentation.d.ts +9 -1
- package/dest/modules/instrumentation.d.ts.map +1 -1
- package/dest/modules/instrumentation.js +23 -2
- package/dest/modules/l1_synchronizer.d.ts +3 -3
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +78 -41
- package/dest/modules/outbox_trees_resolver.d.ts +64 -0
- package/dest/modules/outbox_trees_resolver.d.ts.map +1 -0
- package/dest/modules/outbox_trees_resolver.js +184 -0
- package/dest/modules/validation.d.ts +29 -7
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +31 -13
- package/dest/store/block_store.d.ts +26 -22
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +105 -78
- package/dest/store/contract_class_store.d.ts +1 -1
- package/dest/store/contract_class_store.d.ts.map +1 -1
- package/dest/store/contract_class_store.js +19 -1
- package/dest/store/contract_instance_store.d.ts +1 -1
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +18 -1
- package/dest/store/log_store.d.ts +1 -1
- package/dest/store/log_store.d.ts.map +1 -1
- package/dest/store/log_store.js +3 -3
- package/dest/store/log_store_codec.d.ts +9 -1
- package/dest/store/log_store_codec.d.ts.map +1 -1
- package/dest/store/log_store_codec.js +9 -0
- package/dest/test/fake_l1_state.js +3 -3
- package/dest/test/mock_l1_to_l2_message_source.d.ts +1 -1
- package/dest/test/mock_l1_to_l2_message_source.d.ts.map +1 -1
- package/dest/test/mock_l1_to_l2_message_source.js +1 -2
- package/dest/test/mock_l2_block_source.d.ts +4 -11
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +15 -39
- package/dest/test/mock_structs.d.ts +1 -1
- package/dest/test/mock_structs.d.ts.map +1 -1
- package/dest/test/mock_structs.js +2 -2
- package/dest/test/noop_l1_archiver.d.ts +8 -3
- package/dest/test/noop_l1_archiver.d.ts.map +1 -1
- package/dest/test/noop_l1_archiver.js +12 -9
- package/package.json +13 -13
- package/src/archiver.ts +147 -42
- package/src/config.ts +34 -16
- package/src/factory.ts +48 -4
- package/src/l1/calldata_retriever.ts +13 -6
- package/src/l1/data_retrieval.ts +8 -1
- package/src/modules/contract_data_source_adapter.ts +1 -10
- package/src/modules/data_source_base.ts +17 -44
- package/src/modules/data_store_updater.ts +4 -3
- package/src/modules/instrumentation.ts +15 -2
- package/src/modules/l1_synchronizer.ts +120 -57
- package/src/modules/outbox_trees_resolver.ts +220 -0
- package/src/modules/validation.ts +71 -15
- package/src/store/block_store.ts +108 -101
- package/src/store/contract_class_store.ts +19 -1
- package/src/store/contract_instance_store.ts +18 -1
- package/src/store/log_store.ts +3 -2
- package/src/store/log_store_codec.ts +11 -0
- package/src/test/fake_l1_state.ts +3 -3
- package/src/test/mock_l1_to_l2_message_source.ts +0 -1
- package/src/test/mock_l2_block_source.ts +14 -53
- package/src/test/mock_structs.ts +5 -2
- package/src/test/noop_l1_archiver.ts +30 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/modules/instrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAKL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACpB,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,uBAAuB;IA+BhC,OAAO,CAAC,SAAS;IA9BnB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,oBAAoB,CAAY;IACxC,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,SAAS,CAAc;IAE/B,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,UAAU,CAAgB;IAElC,OAAO,CAAC,oBAAoB,CAAY;IACxC,OAAO,CAAC,yBAAyB,CAAY;IAC7C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,WAAW,CAAY;IAE/B,OAAO,CAAC,sBAAsB,CAAY;IAC1C,OAAO,CAAC,gBAAgB,CAAgB;IAExC,OAAO,CAAC,0BAA0B,CAAgB;IAElD,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,uBAAuB,CAAgB;IAE/C,OAAO,CAAC,GAAG,CAA4C;IAEvD,OAAO,
|
|
1
|
+
{"version":3,"file":"instrumentation.d.ts","sourceRoot":"","sources":["../../src/modules/instrumentation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAElE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAErE,OAAO,EAKL,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACpB,KAAK,MAAM,EAGZ,MAAM,yBAAyB,CAAC;AAEjC,qBAAa,uBAAuB;IA+BhC,OAAO,CAAC,SAAS;IA9BnB,SAAgB,MAAM,EAAE,MAAM,CAAC;IAE/B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,aAAa,CAAQ;IAC7B,OAAO,CAAC,oBAAoB,CAAY;IACxC,OAAO,CAAC,oBAAoB,CAAgB;IAC5C,OAAO,CAAC,SAAS,CAAc;IAE/B,OAAO,CAAC,aAAa,CAAY;IACjC,OAAO,CAAC,UAAU,CAAgB;IAElC,OAAO,CAAC,oBAAoB,CAAY;IACxC,OAAO,CAAC,yBAAyB,CAAY;IAC7C,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,YAAY,CAAY;IAChC,OAAO,CAAC,WAAW,CAAY;IAE/B,OAAO,CAAC,sBAAsB,CAAY;IAC1C,OAAO,CAAC,gBAAgB,CAAgB;IAExC,OAAO,CAAC,0BAA0B,CAAgB;IAElD,OAAO,CAAC,0BAA0B,CAAY;IAC9C,OAAO,CAAC,uBAAuB,CAAgB;IAE/C,OAAO,CAAC,GAAG,CAA4C;IAEvD,OAAO,eA4DN;IAED,OAAoB,GAAG,CAAC,SAAS,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,iBAAiB,oCAMhF;IAEM,SAAS,IAAI,OAAO,CAE1B;IAEM,uBAAuB,CAAC,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,QAStE;IAEM,4BAA4B,CAAC,qBAAqB,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,QASnF;IAEM,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,QAMhE;IAEM,YAAY,CAAC,QAAQ,EAAE,MAAM,QAGnC;IAED;;;;;;OAMG;IACI,WAAW,CAAC,SAAS,EAAE,gBAAgB,GAAG,aAAa,GAAG,QAAQ,GAAG,aAAa,QAExF;IAEM,0BAA0B,CAAC,UAAU,EAAE,cAAc,QAI3D;IAEM,qBAAqB,CAAC,IAAI,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,QAW9F;IAEM,mBAAmB,CAAC,WAAW,EAAE,MAAM,QAE7C;IAEM,2BAA2B,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,QAKpE;IAED,wEAAwE;IACjE,yBAAyB,SAE/B;IAED;;OAEG;IACI,yBAAyB,CAAC,IAAI,EAAE;QACrC,UAAU,EAAE,UAAU,CAAC;QACvB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,GAAG,cAAc,CAAC,CAAC;KACxE,GAAG,IAAI,CAIP;CACF"}
|
|
@@ -48,7 +48,15 @@ export class ArchiverInstrumentation {
|
|
|
48
48
|
this.syncDurationPerMessage = meter.createHistogram(Metrics.ARCHIVER_SYNC_PER_MESSAGE);
|
|
49
49
|
this.syncMessageCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_SYNC_MESSAGE_COUNT);
|
|
50
50
|
this.pruneDuration = meter.createHistogram(Metrics.ARCHIVER_PRUNE_DURATION);
|
|
51
|
-
this.pruneCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_PRUNE_COUNT
|
|
51
|
+
this.pruneCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_PRUNE_COUNT, {
|
|
52
|
+
[Attributes.PRUNE_TYPE]: [
|
|
53
|
+
'unproven',
|
|
54
|
+
'uncheckpointed',
|
|
55
|
+
'l1_conflict',
|
|
56
|
+
'orphan',
|
|
57
|
+
'l1_mismatch'
|
|
58
|
+
]
|
|
59
|
+
});
|
|
52
60
|
this.blockProposalTxTargetCount = createUpDownCounterWithDefault(meter, Metrics.ARCHIVER_BLOCK_PROPOSAL_TX_TARGET_COUNT, {
|
|
53
61
|
[Attributes.L1_BLOCK_PROPOSAL_USED_TRACE]: [
|
|
54
62
|
true,
|
|
@@ -99,9 +107,22 @@ export class ArchiverInstrumentation {
|
|
|
99
107
|
this.syncDurationPerMessage.record(Math.ceil(syncPerMessageMs));
|
|
100
108
|
}
|
|
101
109
|
processPrune(duration) {
|
|
102
|
-
this.pruneCount.add(1
|
|
110
|
+
this.pruneCount.add(1, {
|
|
111
|
+
[Attributes.PRUNE_TYPE]: 'unproven'
|
|
112
|
+
});
|
|
103
113
|
this.pruneDuration.record(Math.ceil(duration));
|
|
104
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* Records a pending-chain reorg, where the archiver dropped proposed blocks (and world-state follows by pruning). The
|
|
117
|
+
* type distinguishes the cause: 'uncheckpointed' (slot ended without a checkpoint), 'l1_conflict' (proposed blocks
|
|
118
|
+
* conflicting with an L1 checkpoint), 'orphan' (no matching proposed checkpoint arrived before the deadline), or
|
|
119
|
+
* 'l1_mismatch' (the local checkpointed tip diverged from L1 — an L1 reorg or a pruned/missed-proof checkpoint — so
|
|
120
|
+
* already-checkpointed blocks were rewound).
|
|
121
|
+
*/ recordPrune(pruneType) {
|
|
122
|
+
this.pruneCount.add(1, {
|
|
123
|
+
[Attributes.PRUNE_TYPE]: pruneType
|
|
124
|
+
});
|
|
125
|
+
}
|
|
105
126
|
updateLastProvenCheckpoint(checkpoint) {
|
|
106
127
|
const lastBlockNumberInCheckpoint = checkpoint.startBlock + checkpoint.blockCount - 1;
|
|
107
128
|
this.blockHeight.record(lastBlockNumberInCheckpoint, {
|
|
@@ -6,7 +6,7 @@ import { Buffer32 } from '@aztec/foundation/buffer';
|
|
|
6
6
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
7
7
|
import { type Logger } from '@aztec/foundation/log';
|
|
8
8
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
9
|
-
import { type ArchiverEmitter } from '@aztec/stdlib/block';
|
|
9
|
+
import { type ArchiverEmitter, type L2Block } from '@aztec/stdlib/block';
|
|
10
10
|
import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
11
11
|
import { type Traceable, type Tracer } from '@aztec/telemetry-client';
|
|
12
12
|
import { type ArchiverDataStores } from '../store/data_stores.js';
|
|
@@ -58,7 +58,7 @@ export declare class ArchiverL1Synchronizer implements Traceable {
|
|
|
58
58
|
private getSignatureContext;
|
|
59
59
|
/** Checks that the ethereum node we are connected to has a latest timestamp no more than the allowed drift. Throw if not. */
|
|
60
60
|
testEthereumNodeSynced(): Promise<void>;
|
|
61
|
-
syncFromL1(initialSyncComplete: boolean): Promise<
|
|
61
|
+
syncFromL1(initialSyncComplete: boolean): Promise<L2Block[]>;
|
|
62
62
|
private updateFinalizedCheckpoint;
|
|
63
63
|
private pruneUncheckpointedBlocks;
|
|
64
64
|
private canPrune;
|
|
@@ -74,4 +74,4 @@ export declare class ArchiverL1Synchronizer implements Traceable {
|
|
|
74
74
|
private checkForNewCheckpointsBeforeL1SyncPoint;
|
|
75
75
|
private getCheckpointHeader;
|
|
76
76
|
}
|
|
77
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
77
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibDFfc3luY2hyb25pemVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kdWxlcy9sMV9zeW5jaHJvbml6ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sb0JBQW9CLENBQUM7QUFDaEQsT0FBTyxFQUFFLGFBQWEsRUFBMkIsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFHbkcsT0FBTyxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUscUJBQXFCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUlyRixPQUFPLEVBQVksUUFBUSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFFOUQsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBRXBELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUdsRSxPQUFPLEVBQUUsWUFBWSxFQUFrQixNQUFNLHlCQUF5QixDQUFDO0FBRXZFLE9BQU8sRUFDTCxLQUFLLGVBQWUsRUFDcEIsS0FBSyxPQUFPLEVBR2IsTUFBTSxxQkFBcUIsQ0FBQztBQVE3QixPQUFPLEVBQUUsS0FBSyxpQkFBaUIsRUFBd0MsTUFBTSw2QkFBNkIsQ0FBQztBQUczRyxPQUFPLEVBQUUsS0FBSyxTQUFTLEVBQUUsS0FBSyxNQUFNLEVBQXlCLE1BQU0seUJBQXlCLENBQUM7QUFZN0YsT0FBTyxFQUFFLEtBQUssa0JBQWtCLEVBQXlCLE1BQU0seUJBQXlCLENBQUM7QUFDekYsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFJN0QsT0FBTyxLQUFLLEVBQUUsdUJBQXVCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQWlCcEU7OztHQUdHO0FBQ0gscUJBQWEsc0JBQXVCLFlBQVcsU0FBUztJQVNwRCxPQUFP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
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"l1_synchronizer.d.ts","sourceRoot":"","sources":["../../src/modules/l1_synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,aAAa,EAA2B,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAGnG,OAAO,KAAK,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAIrF,OAAO,EAAY,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AAEpD,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAGlE,OAAO,EAAE,YAAY,EAAkB,MAAM,yBAAyB,CAAC;AAEvE,OAAO,
|
|
1
|
+
{"version":3,"file":"l1_synchronizer.d.ts","sourceRoot":"","sources":["../../src/modules/l1_synchronizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -393,7 +393,7 @@ import { getCheckpointBlobDataFromBlobs, retrieveCheckpointCalldataFromRollup, r
|
|
|
393
393
|
import { getArchiverSynchPoint } from '../store/data_stores.js';
|
|
394
394
|
import { MessageStoreError } from '../store/message_store.js';
|
|
395
395
|
import { ArchiverDataStoreUpdater } from './data_store_updater.js';
|
|
396
|
-
import {
|
|
396
|
+
import { validateCheckpointAttestationsFromCalldata } from './validation.js';
|
|
397
397
|
_dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpochPrune'), _dec2 = trackSpan('Archiver.handleL1ToL2Messages'), _dec3 = trackSpan('Archiver.handleCheckpoints');
|
|
398
398
|
/**
|
|
399
399
|
* Handles L1 synchronization for the archiver.
|
|
@@ -490,6 +490,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
492
|
async syncFromL1(initialSyncComplete) {
|
|
493
|
+
const blocksAdded = [];
|
|
493
494
|
// In between the various calls to L1, the block number can move meaning some of the following
|
|
494
495
|
// calls will return data for blocks that were not present during earlier calls. To combat this
|
|
495
496
|
// we ensure that all data retrieval methods only retrieve data up to the currentBlockNumber
|
|
@@ -506,7 +507,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
506
507
|
};
|
|
507
508
|
if (this.l1BlockHash && currentL1BlockHash.equals(this.l1BlockHash)) {
|
|
508
509
|
this.log.trace(`No new L1 blocks since last sync at L1 block ${this.l1BlockNumber}`);
|
|
509
|
-
return;
|
|
510
|
+
return blocksAdded;
|
|
510
511
|
}
|
|
511
512
|
// Log at error if the latest L1 block timestamp is too old
|
|
512
513
|
const maxAllowedDelay = this.config.maxAllowedEthClientDriftSeconds;
|
|
@@ -540,6 +541,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
540
541
|
// First we retrieve new checkpoints and L2 blocks and store them in the DB. This will also update the
|
|
541
542
|
// pending chain validation status, proven checkpoint number, and synched L1 block number.
|
|
542
543
|
const rollupStatus = await this.handleCheckpoints(blocksSynchedTo, currentL1BlockNumber, initialSyncComplete);
|
|
544
|
+
blocksAdded.push(...rollupStatus.blocksAdded);
|
|
543
545
|
// Then we try pruning uncheckpointed blocks if a new slot was mined without checkpoints
|
|
544
546
|
await this.pruneUncheckpointedBlocks(currentL1Timestamp);
|
|
545
547
|
// Then we prune the current epoch if it'd reorg on next submission.
|
|
@@ -570,6 +572,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
570
572
|
l1TimestampAtStart: currentL1Timestamp,
|
|
571
573
|
l1BlockNumberAtEnd
|
|
572
574
|
});
|
|
575
|
+
return blocksAdded;
|
|
573
576
|
}
|
|
574
577
|
/** Updates the finalized checkpoint using the pre-fetched finalized L1 block from the current sync iteration. */ async updateFinalizedCheckpoint(finalizedL1Block) {
|
|
575
578
|
try {
|
|
@@ -625,6 +628,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
625
628
|
});
|
|
626
629
|
const prunedBlocks = await this.updater.removeUncheckpointedBlocksAfter(lastCheckpointedBlockNumber);
|
|
627
630
|
if (prunedBlocks.length > 0) {
|
|
631
|
+
this.instrumentation.recordPrune('uncheckpointed');
|
|
628
632
|
this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
|
|
629
633
|
type: L2BlockSourceEvents.L2PruneUncheckpointed,
|
|
630
634
|
slotNumber: firstUncheckpointedBlockSlot,
|
|
@@ -885,6 +889,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
885
889
|
return Buffer32.fromString(block.hash);
|
|
886
890
|
}
|
|
887
891
|
async handleCheckpoints(blocksSynchedTo, currentL1BlockNumber, initialSyncComplete) {
|
|
892
|
+
const blocksAdded = [];
|
|
888
893
|
const localPendingCheckpointNumber = await this.stores.blocks.getLatestCheckpointNumber();
|
|
889
894
|
const initialValidationResult = await this.stores.blocks.getPendingChainValidationStatus();
|
|
890
895
|
const { provenCheckpointNumber, provenArchive, pendingCheckpointNumber, pendingArchive, archiveOfMyCheckpoint: archiveForLocalPendingCheckpointNumber } = await execInSpan(this.tracer, 'Archiver.getRollupStatus', ()=>this.rollup.status(localPendingCheckpointNumber, {
|
|
@@ -895,7 +900,8 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
895
900
|
provenArchive: provenArchive.toString(),
|
|
896
901
|
pendingCheckpointNumber,
|
|
897
902
|
pendingArchive: pendingArchive.toString(),
|
|
898
|
-
validationResult: initialValidationResult
|
|
903
|
+
validationResult: initialValidationResult,
|
|
904
|
+
blocksAdded
|
|
899
905
|
};
|
|
900
906
|
this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
|
|
901
907
|
localPendingCheckpointNumber,
|
|
@@ -1006,6 +1012,9 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1006
1012
|
}
|
|
1007
1013
|
const checkpointsToRemove = localPendingCheckpointNumber - tipAfterUnwind;
|
|
1008
1014
|
await this.updater.removeCheckpointsAfter(CheckpointNumber(tipAfterUnwind));
|
|
1015
|
+
if (checkpointsToRemove > 0) {
|
|
1016
|
+
this.instrumentation.recordPrune('l1_mismatch');
|
|
1017
|
+
}
|
|
1009
1018
|
this.log.warn(`Removed ${count(checkpointsToRemove, 'checkpoint')} after checkpoint ${tipAfterUnwind} ` + `due to mismatched checkpoint hashes at L1 block ${currentL1BlockNumber}. ` + `Updated L2 latest checkpoint is ${await this.stores.blocks.getLatestCheckpointNumber()}.`);
|
|
1010
1019
|
}
|
|
1011
1020
|
}
|
|
@@ -1038,31 +1047,24 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1038
1047
|
const promoteResult = await this.tryBuildPublishedCheckpointFromProposed(lastCalldataCheckpoint);
|
|
1039
1048
|
const checkpointToPromote = promoteResult && !('diverged' in promoteResult) ? promoteResult : undefined;
|
|
1040
1049
|
const evictProposedFrom = promoteResult && 'diverged' in promoteResult ? promoteResult.fromCheckpointNumber : undefined;
|
|
1041
|
-
//
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
...blobFetched,
|
|
1050
|
-
checkpointToPromote
|
|
1051
|
-
] : blobFetched;
|
|
1052
|
-
const validCheckpoints = [];
|
|
1053
|
-
// Now loop through all checkpoints and validate their attestations
|
|
1054
|
-
for (const published of publishedCheckpoints){
|
|
1055
|
-
// Check the attestations uploaded by the publisher to L1 are correct
|
|
1050
|
+
// Validate attestations from CALLDATA before fetching any blobs. A checkpoint with invalid
|
|
1051
|
+
// attestations (or one descending from a rejected ancestor) is rejected here without fetching its
|
|
1052
|
+
// blobs, so a malformed blob does not throw during decode before the rejection path runs and
|
|
1053
|
+
// stall sync. The signed consensus payload (header, archive root, fee asset price
|
|
1054
|
+
// modifier) is fully available from calldata.
|
|
1055
|
+
const checkpointsToIngest = [];
|
|
1056
|
+
for (const calldataCheckpoint of calldataCheckpoints){
|
|
1057
|
+
// Check the attestations uploaded by the publisher to L1 are correct.
|
|
1056
1058
|
// Rollup contract does not validate attestations to save on gas, so this
|
|
1057
1059
|
// falls on the nodes to verify offchain and skip those checkpoints.
|
|
1058
1060
|
const validationResult = this.config.skipValidateCheckpointAttestations ? {
|
|
1059
1061
|
valid: true
|
|
1060
|
-
} : await
|
|
1062
|
+
} : await validateCheckpointAttestationsFromCalldata(calldataCheckpoint, this.epochCache, this.l1Constants, this.getSignatureContext(), this.log);
|
|
1061
1063
|
// Also skip the checkpoint if it builds on a previously-rejected ancestor. Without
|
|
1062
1064
|
// this, addCheckpoints would throw InitialCheckpointNumberNotSequentialError when the
|
|
1063
1065
|
// ancestor was skipped earlier (e.g. due to invalid attestations), the catch handler
|
|
1064
1066
|
// would roll back the L1 sync point, and the next iteration would re-fetch and re-throw.
|
|
1065
|
-
const rejectedAncestor = await this.stores.blocks.getRejectedCheckpointByArchiveRoot(
|
|
1067
|
+
const rejectedAncestor = await this.stores.blocks.getRejectedCheckpointByArchiveRoot(calldataCheckpoint.header.lastArchiveRoot);
|
|
1066
1068
|
// Update the validation result if it has changed, so we can keep track of the first invalid checkpoint
|
|
1067
1069
|
// in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
|
|
1068
1070
|
// There is an exception though: if a checkpoint is invalidated and replaced with another invalid checkpoint,
|
|
@@ -1076,9 +1078,9 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1076
1078
|
rollupStatus.validationResult = validationResult;
|
|
1077
1079
|
}
|
|
1078
1080
|
if (!validationResult.valid) {
|
|
1079
|
-
this.log.warn(`Skipping checkpoint ${
|
|
1080
|
-
|
|
1081
|
-
l1BlockNumber:
|
|
1081
|
+
this.log.warn(`Skipping checkpoint ${calldataCheckpoint.checkpointNumber} due to invalid attestations`, {
|
|
1082
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1083
|
+
l1BlockNumber: calldataCheckpoint.l1.blockNumber,
|
|
1082
1084
|
...pick(validationResult, 'reason')
|
|
1083
1085
|
});
|
|
1084
1086
|
// Emit event for invalid checkpoint detection
|
|
@@ -1090,22 +1092,27 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1090
1092
|
// is detected and skipped (rather than tripping the addCheckpoints consecutive-number
|
|
1091
1093
|
// check and causing the sync point to roll back in a loop).
|
|
1092
1094
|
await this.stores.blocks.addRejectedCheckpoint({
|
|
1093
|
-
checkpointNumber:
|
|
1094
|
-
archiveRoot:
|
|
1095
|
-
parentArchiveRoot:
|
|
1096
|
-
slotNumber:
|
|
1097
|
-
l1:
|
|
1095
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1096
|
+
archiveRoot: calldataCheckpoint.archiveRoot,
|
|
1097
|
+
parentArchiveRoot: calldataCheckpoint.header.lastArchiveRoot,
|
|
1098
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1099
|
+
l1: calldataCheckpoint.l1,
|
|
1098
1100
|
reason: 'invalid-attestations'
|
|
1099
1101
|
});
|
|
1100
1102
|
continue;
|
|
1101
1103
|
}
|
|
1102
1104
|
if (rejectedAncestor) {
|
|
1103
|
-
const descendantInfo =
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1105
|
+
const descendantInfo = {
|
|
1106
|
+
archive: calldataCheckpoint.archiveRoot,
|
|
1107
|
+
lastArchive: calldataCheckpoint.header.lastArchiveRoot,
|
|
1108
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1109
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1110
|
+
timestamp: calldataCheckpoint.header.timestamp
|
|
1111
|
+
};
|
|
1112
|
+
this.log.warn(`Skipping checkpoint ${calldataCheckpoint.checkpointNumber} as it is a descendant of ` + `rejected checkpoint ${rejectedAncestor.checkpointNumber} (${rejectedAncestor.reason})`, {
|
|
1113
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1114
|
+
l1BlockNumber: calldataCheckpoint.l1.blockNumber,
|
|
1115
|
+
l1BlockHash: calldataCheckpoint.l1.blockHash,
|
|
1109
1116
|
ancestorCheckpointNumber: rejectedAncestor.checkpointNumber,
|
|
1110
1117
|
ancestorArchiveRoot: rejectedAncestor.archiveRoot.toString(),
|
|
1111
1118
|
ancestorReason: rejectedAncestor.reason
|
|
@@ -1119,15 +1126,38 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1119
1126
|
// Persist this chainpoint as rejected as well, so we can construct a chain of
|
|
1120
1127
|
// skipped checkpoints starting from the first one with invalid attestations.
|
|
1121
1128
|
await this.stores.blocks.addRejectedCheckpoint({
|
|
1122
|
-
checkpointNumber:
|
|
1123
|
-
archiveRoot:
|
|
1124
|
-
parentArchiveRoot:
|
|
1125
|
-
slotNumber:
|
|
1126
|
-
l1:
|
|
1129
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1130
|
+
archiveRoot: calldataCheckpoint.archiveRoot,
|
|
1131
|
+
parentArchiveRoot: calldataCheckpoint.header.lastArchiveRoot,
|
|
1132
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1133
|
+
l1: calldataCheckpoint.l1,
|
|
1127
1134
|
reason: 'descends-from-invalid-attestations'
|
|
1128
1135
|
});
|
|
1129
1136
|
continue;
|
|
1130
1137
|
}
|
|
1138
|
+
checkpointsToIngest.push(calldataCheckpoint);
|
|
1139
|
+
}
|
|
1140
|
+
// Fetch blobs in parallel only for the surviving (attestation-valid, non-descendant) checkpoints,
|
|
1141
|
+
// then build the full published checkpoints. The last calldata checkpoint may be promotable from a
|
|
1142
|
+
// local proposed block (checkpointToPromote), in which case it carries no blob to fetch. A missing or
|
|
1143
|
+
// undecodable blob throws and propagates, rolling back the L1 sync point so the fetch is retried.
|
|
1144
|
+
const toFetchBlobs = checkpointToPromote ? checkpointsToIngest.filter((c)=>c.checkpointNumber !== checkpointToPromote.checkpoint.number) : checkpointsToIngest;
|
|
1145
|
+
const blobFetched = await asyncPool(10, toFetchBlobs, async (checkpoint)=>retrievedToPublishedCheckpoint({
|
|
1146
|
+
...checkpoint,
|
|
1147
|
+
checkpointBlobData: await getCheckpointBlobDataFromBlobs(this.blobClient, checkpoint.l1.blockHash, checkpoint.blobHashes, checkpoint.checkpointNumber, this.log, !initialSyncComplete, checkpoint.parentBeaconBlockRoot, checkpoint.l1.timestamp)
|
|
1148
|
+
}));
|
|
1149
|
+
// Index the built checkpoints by number so we can ingest them in calldata order, slotting in the
|
|
1150
|
+
// promoted checkpoint (built from a local proposed block rather than blobs).
|
|
1151
|
+
const publishedByNumber = new Map(blobFetched.map((published)=>[
|
|
1152
|
+
published.checkpoint.number,
|
|
1153
|
+
published
|
|
1154
|
+
]));
|
|
1155
|
+
if (checkpointToPromote) {
|
|
1156
|
+
publishedByNumber.set(checkpointToPromote.checkpoint.number, checkpointToPromote);
|
|
1157
|
+
}
|
|
1158
|
+
const validCheckpoints = [];
|
|
1159
|
+
for (const calldataCheckpoint of checkpointsToIngest){
|
|
1160
|
+
const published = publishedByNumber.get(calldataCheckpoint.checkpointNumber);
|
|
1131
1161
|
// Check the inHash of the checkpoint against the l1->l2 messages.
|
|
1132
1162
|
// The messages should've been synced up to the currentL1BlockNumber and must be available for the published
|
|
1133
1163
|
// checkpoints we just retrieved.
|
|
@@ -1172,6 +1202,10 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1172
1202
|
if (validCheckpoints.length > 0) {
|
|
1173
1203
|
this.instrumentation.processNewCheckpointedBlocks(processDuration / validCheckpoints.length, validCheckpoints.flatMap((c)=>c.checkpoint.blocks));
|
|
1174
1204
|
}
|
|
1205
|
+
// Record blocks newly fetched from L1 checkpoint payloads as added. The promoted checkpoint (if any) is
|
|
1206
|
+
// excluded: its blocks were already in local archiver storage (added via the proposed-block path) so the
|
|
1207
|
+
// block stream does not need them re-downloaded.
|
|
1208
|
+
blocksAdded.push(...checkpointsToAdd.flatMap((c)=>c.checkpoint.blocks));
|
|
1175
1209
|
// If blocks were pruned due to conflict with L1 checkpoints, emit event
|
|
1176
1210
|
if (result.prunedBlocks && result.prunedBlocks.length > 0) {
|
|
1177
1211
|
const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
|
|
@@ -1181,6 +1215,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1181
1215
|
prunedSlotNumber,
|
|
1182
1216
|
prunedCheckpointNumber
|
|
1183
1217
|
});
|
|
1218
|
+
this.instrumentation.recordPrune('l1_conflict');
|
|
1184
1219
|
// Emit event for listening services to react to the prune.
|
|
1185
1220
|
// Note: slotNumber comes from the first pruned block. If pruned blocks theoretically spanned multiple slots,
|
|
1186
1221
|
// only one slot number would be reported (though in practice all blocks in a checkpoint span a single slot).
|
|
@@ -1220,7 +1255,9 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1220
1255
|
});
|
|
1221
1256
|
}
|
|
1222
1257
|
lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
|
|
1223
|
-
|
|
1258
|
+
// The last checkpoint seen on L1 this batch (valid or rejected), tracked from calldata since
|
|
1259
|
+
// rejected checkpoints are no longer built into PublishedCheckpoints.
|
|
1260
|
+
lastSeenCheckpoint = lastCalldataCheckpoint;
|
|
1224
1261
|
}while (searchEndBlock < currentL1BlockNumber)
|
|
1225
1262
|
// Important that we update AFTER inserting the blocks.
|
|
1226
1263
|
await updateProvenCheckpoint();
|
|
@@ -1310,7 +1347,7 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1310
1347
|
const { lastSeenCheckpoint, pendingCheckpointNumber } = status;
|
|
1311
1348
|
// Compare the last checkpoint (valid or not) we have (either retrieved in this round or loaded from store)
|
|
1312
1349
|
// with what the rollup contract told us was the latest one (pinned at the currentL1BlockNumber).
|
|
1313
|
-
const latestLocalCheckpointNumber = lastSeenCheckpoint?.
|
|
1350
|
+
const latestLocalCheckpointNumber = lastSeenCheckpoint?.checkpointNumber ?? CheckpointNumber.max(await this.stores.blocks.getLatestCheckpointNumber(), await this.stores.blocks.getLatestRejectedCheckpointNumber()) ?? CheckpointNumber.ZERO;
|
|
1314
1351
|
if (latestLocalCheckpointNumber < pendingCheckpointNumber) {
|
|
1315
1352
|
// Here we have consumed all logs until the `currentL1Block` we pinned at the beginning of the archiver loop,
|
|
1316
1353
|
// but still haven't reached the pending checkpoint according to the call to the rollup contract.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { OutboxContract } from '@aztec/ethereum/contracts';
|
|
2
|
+
import type { BlockNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
|
+
import { type Logger } from '@aztec/foundation/log';
|
|
5
|
+
import type { AztecAsyncKVStore } from '@aztec/kv-store';
|
|
6
|
+
import type { L2Block } from '@aztec/stdlib/block';
|
|
7
|
+
import type { CheckpointData } from '@aztec/stdlib/checkpoint';
|
|
8
|
+
import { type L2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
|
|
9
|
+
import type { IndexedTxEffect, TxHash } from '@aztec/stdlib/tx';
|
|
10
|
+
/**
|
|
11
|
+
* Archiver-side view of the data the resolver needs to assemble a witness. The archiver implements
|
|
12
|
+
* each of these natively, so no L2 RPC plumbing is required.
|
|
13
|
+
*/
|
|
14
|
+
export interface OutboxTreesArchiverView {
|
|
15
|
+
getBlocks(query: {
|
|
16
|
+
epoch: EpochNumber;
|
|
17
|
+
onlyCheckpointed: true;
|
|
18
|
+
}): Promise<L2Block[]>;
|
|
19
|
+
getBlock(query: {
|
|
20
|
+
number: BlockNumber;
|
|
21
|
+
}): Promise<L2Block | undefined>;
|
|
22
|
+
getCheckpointsData(query: {
|
|
23
|
+
epoch: EpochNumber;
|
|
24
|
+
}): Promise<CheckpointData[]>;
|
|
25
|
+
getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Holds an in-memory cache of per-epoch Outbox roots used to build L2-to-L1 message membership
|
|
29
|
+
* witnesses, plus the single-flight registry that dedupes concurrent fetches.
|
|
30
|
+
*
|
|
31
|
+
* Roots are fetched lazily on request and cached against the node's synced L1 block: a cached entry
|
|
32
|
+
* is reused only while the synced L1 block is unchanged, and re-fetched from L1 once the node
|
|
33
|
+
* advances. The cache is intentionally not persisted — entries are valid for at most one L1 slot, so
|
|
34
|
+
* there is nothing worth carrying across restarts. The four merkle-tree levels are rebuilt per
|
|
35
|
+
* request from the archiver's raw L2 data via `computeL2ToL1MembershipWitness`; no trees or
|
|
36
|
+
* intermediate out-hashes are cached.
|
|
37
|
+
*
|
|
38
|
+
* TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so their roots can be frozen
|
|
39
|
+
* and served without any L1 read once they can no longer change. That logic was dropped here to keep
|
|
40
|
+
* the cache simple; it would likely want a persisted store again.
|
|
41
|
+
*/
|
|
42
|
+
export declare class OutboxTreesResolver {
|
|
43
|
+
#private;
|
|
44
|
+
private readonly outbox;
|
|
45
|
+
private readonly archiver;
|
|
46
|
+
private readonly store;
|
|
47
|
+
private readonly getSyncedL1BlockNumber;
|
|
48
|
+
private readonly epochDuration;
|
|
49
|
+
private readonly log;
|
|
50
|
+
constructor(outbox: OutboxContract, archiver: OutboxTreesArchiverView, store: AztecAsyncKVStore, getSyncedL1BlockNumber: () => Promise<bigint | undefined>, epochDuration: number, log?: Logger);
|
|
51
|
+
/**
|
|
52
|
+
* Builds the L2-to-L1 membership witness for `message` in tx `txHash`. Returns `undefined` if the
|
|
53
|
+
* tx isn't yet in a block/epoch, or if the Outbox holds no covering partial-proof root for the
|
|
54
|
+
* tx's checkpoint as of the node's synced L1 block — callers should retry in that case.
|
|
55
|
+
*/
|
|
56
|
+
getL2ToL1MembershipWitness(txHash: TxHash, message: Fr, messageIndexInTx?: number): Promise<L2ToL1MembershipWitness | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* Builds the node-shaped adapter handed to `computeL2ToL1MembershipWitness`. The helper invokes
|
|
59
|
+
* `getL2ToL1Messages` / `getBlock` / `getCheckpointsData`; `getTxReceipt` is part of the helper's
|
|
60
|
+
* node type but is never invoked because we always pass an already-resolved receipt.
|
|
61
|
+
*/
|
|
62
|
+
private makeNodeView;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3V0Ym94X3RyZWVzX3Jlc29sdmVyLmQudHMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvbW9kdWxlcy9vdXRib3hfdHJlZXNfcmVzb2x2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLEVBQUUsY0FBYyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDaEUsT0FBTyxLQUFLLEVBQUUsV0FBVyxFQUFFLFdBQVcsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRWhGLE9BQU8sS0FBSyxFQUFFLEVBQUUsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ3pELE9BQU8sRUFBRSxLQUFLLE1BQU0sRUFBZ0IsTUFBTSx1QkFBdUIsQ0FBQztBQUNsRSxPQUFPLEtBQUssRUFBRSxpQkFBaUIsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3pELE9BQU8sS0FBSyxFQUFFLE9BQU8sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ25ELE9BQU8sS0FBSyxFQUFFLGNBQWMsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRS9ELE9BQU8sRUFBRSxLQUFLLHVCQUF1QixFQUFrQyxNQUFNLHlCQUF5QixDQUFDO0FBQ3ZHLE9BQU8sS0FBSyxFQUFFLGVBQWUsRUFBRSxNQUFNLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUVoRTs7O0dBR0c7QUFDSCxNQUFNLFdBQVcsdUJBQXVCO0lBQ3RDLFNBQVMsQ0FBQyxLQUFLLEVBQUU7UUFBRSxLQUFLLEVBQUUsV0FBVyxDQUFDO1FBQUMsZ0JBQWdCLEVBQUUsSUFBSSxDQUFBO0tBQUUsR0FBRyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUMsQ0FBQztJQUNyRixRQUFRLENBQUMsS0FBSyxFQUFFO1FBQUUsTUFBTSxFQUFFLFdBQVcsQ0FBQTtLQUFFLEdBQUcsT0FBTyxDQUFDLE9BQU8sR0FBRyxTQUFTLENBQUMsQ0FBQztJQUN2RSxrQkFBa0IsQ0FBQyxLQUFLLEVBQUU7UUFBRSxLQUFLLEVBQUUsV0FBVyxDQUFBO0tBQUUsR0FBRyxPQUFPLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztJQUM3RSxXQUFXLENBQUMsTUFBTSxFQUFFLE1BQU0sR0FBRyxPQUFPLENBQUMsZUFBZSxHQUFHLFNBQVMsQ0FBQyxDQUFDO0NBQ25FO0FBVUQ7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxxQkFBYSxtQkFBbUI7O0lBVzVCLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTTtJQUN2QixPQUFPLENBQUMsUUFBUSxDQUFDLFFBQVE7SUFDekIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxLQUFLO0lBQ3RCLE9BQU8sQ0FBQyxRQUFRLENBQUMsc0JBQXNCO0lBQ3ZDLE9BQU8sQ0FBQyxRQUFRLENBQUMsYUFBYTtJQUM5QixPQUFPLENBQUMsUUFBUSxDQUFDLEdBQUc7SUFOdEIsWUFDbUIsTUFBTSxFQUFFLGNBQWMsRUFDdEIsUUFBUSxFQUFFLHVCQUF1QixFQUNqQyxLQUFLLEVBQUUsaUJBQWlCLEVBQ3hCLHNCQUFzQixFQUFFLE1BQU0sT0FBTyxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUMsRUFDekQsYUFBYSxFQUFFLE1BQU0sRUFDckIsR0FBRyxHQUFFLE1BQXdDLEVBRy9EO0lBRUQ7Ozs7T0FJRztJQUNVLDBCQUEwQixDQUNyQyxNQUFNLEVBQUUsTUFBTSxFQUNkLE9BQU8sRUFBRSxFQUFFLEVBQ1gsZ0JBQWdCLENBQUMsRUFBRSxNQUFNLEdBQ3hCLE9BQU8sQ0FBQyx1QkFBdUIsR0FBRyxTQUFTLENBQUMsQ0E2RDlDO0lBaUVEOzs7O09BSUc7SUFDSCxPQUFPLENBQUMsWUFBWTtDQVlyQiJ9
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbox_trees_resolver.d.ts","sourceRoot":"","sources":["../../src/modules/outbox_trees_resolver.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AAEhF,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,EAAE,KAAK,uBAAuB,EAAkC,MAAM,yBAAyB,CAAC;AACvG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEhE;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,WAAW,CAAC;QAAC,gBAAgB,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACrF,QAAQ,CAAC,KAAK,EAAE;QAAE,MAAM,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC;IACvE,kBAAkB,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC7E,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC;CACnE;AAUD;;;;;;;;;;;;;;GAcG;AACH,qBAAa,mBAAmB;;IAW5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,sBAAsB;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,GAAG;IANtB,YACmB,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,uBAAuB,EACjC,KAAK,EAAE,iBAAiB,EACxB,sBAAsB,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EACzD,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,MAAwC,EAG/D;IAED;;;;OAIG;IACU,0BAA0B,CACrC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,EAAE,EACX,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,uBAAuB,GAAG,SAAS,CAAC,CA6D9C;IAiED;;;;OAIG;IACH,OAAO,CAAC,YAAY;CAYrB"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { chunkBy } from '@aztec/foundation/collection';
|
|
2
|
+
import { createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import { getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
4
|
+
import { computeL2ToL1MembershipWitness } from '@aztec/stdlib/messaging';
|
|
5
|
+
/**
|
|
6
|
+
* Holds an in-memory cache of per-epoch Outbox roots used to build L2-to-L1 message membership
|
|
7
|
+
* witnesses, plus the single-flight registry that dedupes concurrent fetches.
|
|
8
|
+
*
|
|
9
|
+
* Roots are fetched lazily on request and cached against the node's synced L1 block: a cached entry
|
|
10
|
+
* is reused only while the synced L1 block is unchanged, and re-fetched from L1 once the node
|
|
11
|
+
* advances. The cache is intentionally not persisted — entries are valid for at most one L1 slot, so
|
|
12
|
+
* there is nothing worth carrying across restarts. The four merkle-tree levels are rebuilt per
|
|
13
|
+
* request from the archiver's raw L2 data via `computeL2ToL1MembershipWitness`; no trees or
|
|
14
|
+
* intermediate out-hashes are cached.
|
|
15
|
+
*
|
|
16
|
+
* TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so their roots can be frozen
|
|
17
|
+
* and served without any L1 read once they can no longer change. That logic was dropped here to keep
|
|
18
|
+
* the cache simple; it would likely want a persisted store again.
|
|
19
|
+
*/ export class OutboxTreesResolver {
|
|
20
|
+
outbox;
|
|
21
|
+
archiver;
|
|
22
|
+
store;
|
|
23
|
+
getSyncedL1BlockNumber;
|
|
24
|
+
epochDuration;
|
|
25
|
+
log;
|
|
26
|
+
/** In-memory cache of per-epoch Outbox roots, keyed by epoch number. */ #cache;
|
|
27
|
+
/** Single-flight registry for in-flight roots fetches, keyed by epoch number. */ #pending;
|
|
28
|
+
/** Adapter exposing the slice of the node API that `computeL2ToL1MembershipWitness` requires. */ #node;
|
|
29
|
+
constructor(outbox, archiver, store, getSyncedL1BlockNumber, epochDuration, log = createLogger('archiver:outbox')){
|
|
30
|
+
this.outbox = outbox;
|
|
31
|
+
this.archiver = archiver;
|
|
32
|
+
this.store = store;
|
|
33
|
+
this.getSyncedL1BlockNumber = getSyncedL1BlockNumber;
|
|
34
|
+
this.epochDuration = epochDuration;
|
|
35
|
+
this.log = log;
|
|
36
|
+
this.#cache = new Map();
|
|
37
|
+
this.#pending = new Map();
|
|
38
|
+
this.#node = this.makeNodeView();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds the L2-to-L1 membership witness for `message` in tx `txHash`. Returns `undefined` if the
|
|
42
|
+
* tx isn't yet in a block/epoch, or if the Outbox holds no covering partial-proof root for the
|
|
43
|
+
* tx's checkpoint as of the node's synced L1 block — callers should retry in that case.
|
|
44
|
+
*/ async getL2ToL1MembershipWitness(txHash, message, messageIndexInTx) {
|
|
45
|
+
// Read the tx effect once up front only to learn which epoch's Outbox roots to fetch. The roots
|
|
46
|
+
// read hits L1, so it stays outside the store transaction below: holding the archiver's writer
|
|
47
|
+
// lock across a network round-trip would stall block sync.
|
|
48
|
+
const indexed = await this.archiver.getTxEffect(txHash);
|
|
49
|
+
if (!indexed) {
|
|
50
|
+
this.log.trace(`No tx effect for tx, no witness available`, {
|
|
51
|
+
txHash
|
|
52
|
+
});
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
// Derive the epoch from the tx's slot the same way the node assembles a mined receipt, so the
|
|
56
|
+
// witness helper sees the same view a `getTxReceipt` caller would.
|
|
57
|
+
const epochNumber = getEpochAtSlot(indexed.slotNumber, {
|
|
58
|
+
epochDuration: this.epochDuration
|
|
59
|
+
});
|
|
60
|
+
const roots = await this.#getRoots(epochNumber);
|
|
61
|
+
if (roots === undefined) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const syncedBefore = await this.getSyncedL1BlockNumber();
|
|
65
|
+
try {
|
|
66
|
+
// Assemble the witness inside a single store transaction so every archiver read it depends on
|
|
67
|
+
// (the tx effect, the epoch's blocks, the target block and the checkpoint metadata) observes one
|
|
68
|
+
// consistent snapshot. `transactionAsync` serializes against writers, and the substore reads
|
|
69
|
+
// below bind to it automatically, so no store commit can land mid-assembly and splice together
|
|
70
|
+
// two different chain states — which would otherwise surface as a spurious "message does not
|
|
71
|
+
// exist" error even when the tx-effect index is healthy.
|
|
72
|
+
return await this.store.transactionAsync(async ()=>{
|
|
73
|
+
// Re-read the tx effect within the snapshot so the receipt's block number and tx index stay
|
|
74
|
+
// consistent with the block and checkpoint data read while building the witness. The epoch is
|
|
75
|
+
// reused from the pre-transaction read (it selected the roots above); if a reorg moved the tx
|
|
76
|
+
// to a different epoch in between, the block lookups simply miss and the helper returns
|
|
77
|
+
// undefined for the caller to retry.
|
|
78
|
+
const indexedInSnapshot = await this.archiver.getTxEffect(txHash);
|
|
79
|
+
if (!indexedInSnapshot) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const receipt = {
|
|
83
|
+
txHash,
|
|
84
|
+
epochNumber,
|
|
85
|
+
blockNumber: indexedInSnapshot.l2BlockNumber,
|
|
86
|
+
txIndexInBlock: indexedInSnapshot.txIndexInBlock
|
|
87
|
+
};
|
|
88
|
+
return await computeL2ToL1MembershipWitness(this.#node, roots, message, receipt, messageIndexInTx);
|
|
89
|
+
});
|
|
90
|
+
} catch (err) {
|
|
91
|
+
// The helper throws if the locally-assembled epoch root disagrees with the cached Outbox root.
|
|
92
|
+
// The cached roots are pinned to the node's synced L1 block, but the block / checkpoint data is
|
|
93
|
+
// read from a store snapshot taken after that fetch, so if the archiver advanced in between the
|
|
94
|
+
// two can reflect different heights and produce a transient mismatch. When the synced block moved
|
|
95
|
+
// during assembly we treat the mismatch as transient and return undefined so the caller can
|
|
96
|
+
// retry; a mismatch at a stable synced block is a genuine node/L1 disagreement and must surface.
|
|
97
|
+
const syncedAfter = await this.getSyncedL1BlockNumber();
|
|
98
|
+
if (syncedBefore !== syncedAfter && err instanceof Error && err.message.includes('does not match Outbox')) {
|
|
99
|
+
this.log.debug(`Transient outbox root mismatch during sync, returning no witness`, {
|
|
100
|
+
txHash,
|
|
101
|
+
epoch: epochNumber
|
|
102
|
+
});
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Lazily returns the per-epoch Outbox roots, reusing the cached entry while the node's synced L1
|
|
110
|
+
* block is unchanged and re-fetching from L1 once it advances. Returns `undefined` if the node has
|
|
111
|
+
* not established a synced L1 block yet. Concurrent requests for the same epoch share a single
|
|
112
|
+
* in-flight fetch via {@link #pending}.
|
|
113
|
+
*/ #getRoots(epoch) {
|
|
114
|
+
const inFlight = this.#pending.get(epoch);
|
|
115
|
+
if (inFlight) {
|
|
116
|
+
return inFlight;
|
|
117
|
+
}
|
|
118
|
+
const promise = this.#refreshRoots(epoch).finally(()=>{
|
|
119
|
+
this.#pending.delete(epoch);
|
|
120
|
+
});
|
|
121
|
+
this.#pending.set(epoch, promise);
|
|
122
|
+
return promise;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Single-flight body for {@link #getRoots}. Re-reads the cached entry and synced block here —
|
|
126
|
+
* rather than capturing them before winning the {@link #pending} slot — so a late entrant that
|
|
127
|
+
* missed the slot cannot overwrite a fresher entry the previous winner just wrote.
|
|
128
|
+
*/ async #refreshRoots(epoch) {
|
|
129
|
+
const synced = await this.getSyncedL1BlockNumber();
|
|
130
|
+
if (synced === undefined) {
|
|
131
|
+
// The archiver has no synced L1 block yet, so there is no consistent height to read the Outbox
|
|
132
|
+
// at. The caller retries once the node has synced.
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
const cached = this.#cache.get(epoch);
|
|
136
|
+
if (cached && cached.fetchedAtL1Block === synced) {
|
|
137
|
+
return cached.roots;
|
|
138
|
+
}
|
|
139
|
+
// TODO: Re-add permanent caching for sealed-and-L1-finalized epochs so we can skip the L1 read
|
|
140
|
+
// entirely once an epoch's roots can no longer change. For now the cache is valid only for the
|
|
141
|
+
// current synced L1 block and we re-query the Outbox whenever the node advances.
|
|
142
|
+
const roots = await this.outbox.getRoots(epoch, {
|
|
143
|
+
blockNumber: synced
|
|
144
|
+
});
|
|
145
|
+
this.#cache.set(epoch, {
|
|
146
|
+
roots,
|
|
147
|
+
fetchedAtL1Block: synced
|
|
148
|
+
});
|
|
149
|
+
this.log.trace(`Fetched outbox roots for epoch ${epoch}`, {
|
|
150
|
+
epoch,
|
|
151
|
+
syncedL1Block: synced,
|
|
152
|
+
nonZeroRoots: roots.filter((r)=>!r.isZero()).length
|
|
153
|
+
});
|
|
154
|
+
return roots;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Returns the L2-to-L1 messages in `epoch`, organized as `checkpoint[] → block[] → tx[] →
|
|
158
|
+
* message[]`. Mirrors `AztecNodeService.getL2ToL1Messages` so the resolver can run off the
|
|
159
|
+
* archiver directly without going through the node RPC surface.
|
|
160
|
+
*/ async #getL2ToL1Messages(epoch) {
|
|
161
|
+
const blocks = await this.archiver.getBlocks({
|
|
162
|
+
epoch,
|
|
163
|
+
onlyCheckpointed: true
|
|
164
|
+
});
|
|
165
|
+
const blocksInCheckpoints = chunkBy(blocks, (block)=>block.header.globalVariables.slotNumber);
|
|
166
|
+
return blocksInCheckpoints.map((slotBlocks)=>slotBlocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Builds the node-shaped adapter handed to `computeL2ToL1MembershipWitness`. The helper invokes
|
|
170
|
+
* `getL2ToL1Messages` / `getBlock` / `getCheckpointsData`; `getTxReceipt` is part of the helper's
|
|
171
|
+
* node type but is never invoked because we always pass an already-resolved receipt.
|
|
172
|
+
*/ makeNodeView() {
|
|
173
|
+
return {
|
|
174
|
+
getL2ToL1Messages: (epoch)=>this.#getL2ToL1Messages(epoch),
|
|
175
|
+
getBlock: (param)=>this.archiver.getBlock({
|
|
176
|
+
number: param
|
|
177
|
+
}),
|
|
178
|
+
getCheckpointsData: (query)=>this.archiver.getCheckpointsData(query),
|
|
179
|
+
getTxReceipt: ()=>{
|
|
180
|
+
throw new Error('OutboxTreesResolver always passes a resolved receipt; getTxReceipt must not be called');
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|