@aztec/archiver 1.2.0 → 2.0.0-nightly.20250813
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/archiver.d.ts +15 -5
- package/dest/archiver/archiver.d.ts.map +1 -1
- package/dest/archiver/archiver.js +76 -27
- package/dest/archiver/data_retrieval.d.ts.map +1 -1
- package/dest/archiver/data_retrieval.js +13 -2
- package/dest/archiver/kv_archiver_store/contract_instance_store.js +5 -5
- package/dest/archiver/kv_archiver_store/log_store.js +1 -1
- package/dest/archiver/validation.d.ts +11 -0
- package/dest/archiver/validation.d.ts.map +1 -0
- package/dest/archiver/validation.js +76 -0
- package/dest/factory.d.ts +3 -4
- package/dest/factory.d.ts.map +1 -1
- package/dest/factory.js +3 -7
- package/dest/test/mock_l2_block_source.d.ts +3 -1
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +10 -2
- package/package.json +13 -12
- package/src/archiver/archiver.ts +108 -32
- package/src/archiver/data_retrieval.ts +16 -2
- package/src/archiver/kv_archiver_store/contract_instance_store.ts +5 -5
- package/src/archiver/kv_archiver_store/log_store.ts +1 -1
- package/src/archiver/validation.ts +70 -0
- package/src/factory.ts +3 -6
- package/src/test/mock_l2_block_source.ts +11 -3
|
@@ -2,7 +2,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
|
|
|
2
2
|
import type { Fr } from '@aztec/foundation/fields';
|
|
3
3
|
import type { FunctionSelector } from '@aztec/stdlib/abi';
|
|
4
4
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
5
|
-
import { L2Block, L2BlockHash, type L2BlockSource, type L2Tips } from '@aztec/stdlib/block';
|
|
5
|
+
import { L2Block, L2BlockHash, type L2BlockSource, type L2Tips, type ValidateBlockResult } from '@aztec/stdlib/block';
|
|
6
6
|
import type { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/stdlib/contract';
|
|
7
7
|
import { type L1RollupConstants } from '@aztec/stdlib/epoch-helpers';
|
|
8
8
|
import { type BlockHeader, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
@@ -101,5 +101,7 @@ export declare class MockL2BlockSource implements L2BlockSource, ContractDataSou
|
|
|
101
101
|
getDebugFunctionName(_address: AztecAddress, _selector: FunctionSelector): Promise<string | undefined>;
|
|
102
102
|
registerContractFunctionSignatures(_signatures: string[]): Promise<void>;
|
|
103
103
|
syncImmediate(): Promise<void>;
|
|
104
|
+
isPendingChainInvalid(): Promise<boolean>;
|
|
105
|
+
getPendingChainValidationStatus(): Promise<ValidateBlockResult>;
|
|
104
106
|
}
|
|
105
107
|
//# sourceMappingURL=mock_l2_block_source.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock_l2_block_source.d.ts","sourceRoot":"","sources":["../../src/test/mock_l2_block_source.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AAEnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,aAAa,EAAE,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"mock_l2_block_source.d.ts","sourceRoot":"","sources":["../../src/test/mock_l2_block_source.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@ import { Buffer32 } from '@aztec/foundation/buffer';
|
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { createLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { L2Block, L2BlockHash } from '@aztec/stdlib/block';
|
|
6
|
-
import { getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
6
|
+
import { EmptyL1RollupConstants, getSlotRangeForEpoch } from '@aztec/stdlib/epoch-helpers';
|
|
7
7
|
import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
8
8
|
/**
|
|
9
9
|
* A mocked implementation of L2BlockSource to be used in tests.
|
|
@@ -170,7 +170,7 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
|
170
170
|
throw new Error('Method not implemented.');
|
|
171
171
|
}
|
|
172
172
|
getL1Constants() {
|
|
173
|
-
|
|
173
|
+
return Promise.resolve(EmptyL1RollupConstants);
|
|
174
174
|
}
|
|
175
175
|
getL1Timestamp() {
|
|
176
176
|
throw new Error('Method not implemented.');
|
|
@@ -210,4 +210,12 @@ import { TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
|
210
210
|
syncImmediate() {
|
|
211
211
|
return Promise.resolve();
|
|
212
212
|
}
|
|
213
|
+
isPendingChainInvalid() {
|
|
214
|
+
return Promise.resolve(false);
|
|
215
|
+
}
|
|
216
|
+
getPendingChainValidationStatus() {
|
|
217
|
+
return Promise.resolve({
|
|
218
|
+
valid: true
|
|
219
|
+
});
|
|
220
|
+
}
|
|
213
221
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/archiver",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-nightly.20250813",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -66,17 +66,18 @@
|
|
|
66
66
|
]
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@aztec/blob-lib": "
|
|
70
|
-
"@aztec/blob-sink": "
|
|
71
|
-
"@aztec/constants": "
|
|
72
|
-
"@aztec/
|
|
73
|
-
"@aztec/
|
|
74
|
-
"@aztec/
|
|
75
|
-
"@aztec/
|
|
76
|
-
"@aztec/
|
|
77
|
-
"@aztec/protocol-
|
|
78
|
-
"@aztec/
|
|
79
|
-
"@aztec/
|
|
69
|
+
"@aztec/blob-lib": "2.0.0-nightly.20250813",
|
|
70
|
+
"@aztec/blob-sink": "2.0.0-nightly.20250813",
|
|
71
|
+
"@aztec/constants": "2.0.0-nightly.20250813",
|
|
72
|
+
"@aztec/epoch-cache": "2.0.0-nightly.20250813",
|
|
73
|
+
"@aztec/ethereum": "2.0.0-nightly.20250813",
|
|
74
|
+
"@aztec/foundation": "2.0.0-nightly.20250813",
|
|
75
|
+
"@aztec/kv-store": "2.0.0-nightly.20250813",
|
|
76
|
+
"@aztec/l1-artifacts": "2.0.0-nightly.20250813",
|
|
77
|
+
"@aztec/noir-protocol-circuits-types": "2.0.0-nightly.20250813",
|
|
78
|
+
"@aztec/protocol-contracts": "2.0.0-nightly.20250813",
|
|
79
|
+
"@aztec/stdlib": "2.0.0-nightly.20250813",
|
|
80
|
+
"@aztec/telemetry-client": "2.0.0-nightly.20250813",
|
|
80
81
|
"lodash.groupby": "^4.6.0",
|
|
81
82
|
"lodash.omit": "^4.5.0",
|
|
82
83
|
"tsc-watch": "^6.0.0",
|
package/src/archiver/archiver.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
2
|
+
import { EpochCache } from '@aztec/epoch-cache';
|
|
2
3
|
import {
|
|
3
4
|
BlockTagTooOldError,
|
|
4
5
|
InboxContract,
|
|
@@ -9,24 +10,25 @@ import {
|
|
|
9
10
|
} from '@aztec/ethereum';
|
|
10
11
|
import { maxBigint } from '@aztec/foundation/bigint';
|
|
11
12
|
import { Buffer16, Buffer32 } from '@aztec/foundation/buffer';
|
|
13
|
+
import { pick } from '@aztec/foundation/collection';
|
|
12
14
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
13
15
|
import { Fr } from '@aztec/foundation/fields';
|
|
14
16
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
15
17
|
import { RunningPromise, makeLoggingErrorHandler } from '@aztec/foundation/running-promise';
|
|
16
18
|
import { sleep } from '@aztec/foundation/sleep';
|
|
17
19
|
import { count } from '@aztec/foundation/string';
|
|
18
|
-
import { Timer, elapsed } from '@aztec/foundation/timer';
|
|
20
|
+
import { DateProvider, Timer, elapsed } from '@aztec/foundation/timer';
|
|
19
21
|
import type { CustomRange } from '@aztec/kv-store';
|
|
20
22
|
import { RollupAbi } from '@aztec/l1-artifacts';
|
|
21
23
|
import {
|
|
22
|
-
|
|
24
|
+
ContractClassPublishedEvent,
|
|
23
25
|
PrivateFunctionBroadcastedEvent,
|
|
24
26
|
UtilityFunctionBroadcastedEvent,
|
|
25
|
-
} from '@aztec/protocol-contracts/class-
|
|
27
|
+
} from '@aztec/protocol-contracts/class-registry';
|
|
26
28
|
import {
|
|
27
|
-
|
|
29
|
+
ContractInstancePublishedEvent,
|
|
28
30
|
ContractInstanceUpdatedEvent,
|
|
29
|
-
} from '@aztec/protocol-contracts/instance-
|
|
31
|
+
} from '@aztec/protocol-contracts/instance-registry';
|
|
30
32
|
import type { FunctionSelector } from '@aztec/stdlib/abi';
|
|
31
33
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
32
34
|
import {
|
|
@@ -61,7 +63,14 @@ import { ContractClassLog, type LogFilter, type PrivateLog, type PublicLog, TxSc
|
|
|
61
63
|
import type { L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
62
64
|
import { type BlockHeader, type IndexedTxEffect, TxHash, TxReceipt } from '@aztec/stdlib/tx';
|
|
63
65
|
import type { UInt64 } from '@aztec/stdlib/types';
|
|
64
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
Attributes,
|
|
68
|
+
type TelemetryClient,
|
|
69
|
+
type Traceable,
|
|
70
|
+
type Tracer,
|
|
71
|
+
getTelemetryClient,
|
|
72
|
+
trackSpan,
|
|
73
|
+
} from '@aztec/telemetry-client';
|
|
65
74
|
|
|
66
75
|
import { EventEmitter } from 'events';
|
|
67
76
|
import groupBy from 'lodash.groupby';
|
|
@@ -79,12 +88,20 @@ import { InitialBlockNumberNotSequentialError, NoBlobBodiesFoundError } from './
|
|
|
79
88
|
import { ArchiverInstrumentation } from './instrumentation.js';
|
|
80
89
|
import type { InboxMessage } from './structs/inbox_message.js';
|
|
81
90
|
import type { PublishedL2Block } from './structs/published.js';
|
|
91
|
+
import { type ValidateBlockResult, validateBlockAttestations } from './validation.js';
|
|
82
92
|
|
|
83
93
|
/**
|
|
84
94
|
* Helper interface to combine all sources this archiver implementation provides.
|
|
85
95
|
*/
|
|
86
96
|
export type ArchiveSource = L2BlockSource & L2LogsSource & ContractDataSource & L1ToL2MessageSource;
|
|
87
97
|
|
|
98
|
+
export type ArchiverDeps = {
|
|
99
|
+
telemetry?: TelemetryClient;
|
|
100
|
+
blobSinkClient: BlobSinkClientInterface;
|
|
101
|
+
epochCache?: EpochCache;
|
|
102
|
+
dateProvider?: DateProvider;
|
|
103
|
+
};
|
|
104
|
+
|
|
88
105
|
/**
|
|
89
106
|
* Pulls L2 blocks in a non-blocking manner and provides interface for their retrieval.
|
|
90
107
|
* Responsible for handling robust L1 polling so that other components do not need to
|
|
@@ -103,6 +120,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
103
120
|
|
|
104
121
|
private l1BlockNumber: bigint | undefined;
|
|
105
122
|
private l1Timestamp: bigint | undefined;
|
|
123
|
+
private pendingChainValidationStatus: ValidateBlockResult = { valid: true };
|
|
106
124
|
private initialSyncComplete: boolean = false;
|
|
107
125
|
|
|
108
126
|
public readonly tracer: Tracer;
|
|
@@ -123,6 +141,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
123
141
|
readonly dataStore: ArchiverDataStore,
|
|
124
142
|
private readonly config: { pollingIntervalMs: number; batchSize: number },
|
|
125
143
|
private readonly blobSinkClient: BlobSinkClientInterface,
|
|
144
|
+
private readonly epochCache: EpochCache,
|
|
126
145
|
private readonly instrumentation: ArchiverInstrumentation,
|
|
127
146
|
private readonly l1constants: L1RollupConstants & { l1StartBlockHash: Buffer32 },
|
|
128
147
|
private readonly log: Logger = createLogger('archiver'),
|
|
@@ -146,7 +165,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
146
165
|
public static async createAndSync(
|
|
147
166
|
config: ArchiverConfig,
|
|
148
167
|
archiverStore: ArchiverDataStore,
|
|
149
|
-
deps:
|
|
168
|
+
deps: ArchiverDeps,
|
|
150
169
|
blockUntilSynced = true,
|
|
151
170
|
): Promise<Archiver> {
|
|
152
171
|
const chain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
@@ -185,13 +204,17 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
185
204
|
batchSize: config.archiverBatchSize ?? 100,
|
|
186
205
|
};
|
|
187
206
|
|
|
207
|
+
const epochCache = deps.epochCache ?? (await EpochCache.create(config.l1Contracts.rollupAddress, config, deps));
|
|
208
|
+
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
209
|
+
|
|
188
210
|
const archiver = new Archiver(
|
|
189
211
|
publicClient,
|
|
190
212
|
config.l1Contracts,
|
|
191
213
|
archiverStore,
|
|
192
214
|
opts,
|
|
193
215
|
deps.blobSinkClient,
|
|
194
|
-
|
|
216
|
+
epochCache,
|
|
217
|
+
await ArchiverInstrumentation.new(telemetry, () => archiverStore.estimateSize()),
|
|
195
218
|
l1Constants,
|
|
196
219
|
);
|
|
197
220
|
await archiver.start(blockUntilSynced);
|
|
@@ -331,10 +354,22 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
331
354
|
currentL1BlockNumber,
|
|
332
355
|
currentL1Timestamp,
|
|
333
356
|
);
|
|
357
|
+
|
|
358
|
+
// Update the pending chain validation status with the last block validation result.
|
|
359
|
+
// Again, we only update if validation status changed, so in a sequence of invalid blocks
|
|
360
|
+
// we keep track of the first invalid block so we can invalidate that one if needed.
|
|
361
|
+
if (
|
|
362
|
+
rollupStatus.validationResult &&
|
|
363
|
+
rollupStatus.validationResult?.valid !== this.pendingChainValidationStatus.valid
|
|
364
|
+
) {
|
|
365
|
+
this.pendingChainValidationStatus = rollupStatus.validationResult;
|
|
366
|
+
}
|
|
367
|
+
|
|
334
368
|
// And lastly we check if we are missing any L2 blocks behind us due to a possible L1 reorg.
|
|
335
369
|
// We only do this if rollup cant prune on the next submission. Otherwise we will end up
|
|
336
|
-
// re-syncing the blocks we have just unwound above.
|
|
337
|
-
|
|
370
|
+
// re-syncing the blocks we have just unwound above. We also dont do this if the last block is invalid,
|
|
371
|
+
// since the archiver will rightfully refuse to sync up to it.
|
|
372
|
+
if (!rollupCanPrune && this.pendingChainValidationStatus.valid) {
|
|
338
373
|
await this.checkForNewBlocksBeforeL1SyncPoint(rollupStatus, blocksSynchedTo, currentL1BlockNumber);
|
|
339
374
|
}
|
|
340
375
|
|
|
@@ -595,6 +630,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
595
630
|
provenArchive,
|
|
596
631
|
pendingBlockNumber: Number(pendingBlockNumber),
|
|
597
632
|
pendingArchive,
|
|
633
|
+
validationResult: undefined as ValidateBlockResult | undefined,
|
|
598
634
|
};
|
|
599
635
|
this.log.trace(`Retrieved rollup status at current L1 block ${currentL1BlockNumber}.`, {
|
|
600
636
|
localPendingBlockNumber,
|
|
@@ -680,7 +716,8 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
680
716
|
throw new Error(`Missing block ${localPendingBlockNumber}`);
|
|
681
717
|
}
|
|
682
718
|
|
|
683
|
-
const
|
|
719
|
+
const localPendingArchiveRoot = localPendingBlock.archive.root.toString();
|
|
720
|
+
const noBlockSinceLast = localPendingBlock && pendingArchive === localPendingArchiveRoot;
|
|
684
721
|
if (noBlockSinceLast) {
|
|
685
722
|
// We believe the following line causes a problem when we encounter L1 re-orgs.
|
|
686
723
|
// Basically, by setting the synched L1 block number here, we are saying that we have
|
|
@@ -694,13 +731,16 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
694
731
|
return rollupStatus;
|
|
695
732
|
}
|
|
696
733
|
|
|
697
|
-
const localPendingBlockInChain = archiveForLocalPendingBlockNumber ===
|
|
734
|
+
const localPendingBlockInChain = archiveForLocalPendingBlockNumber === localPendingArchiveRoot;
|
|
698
735
|
if (!localPendingBlockInChain) {
|
|
699
736
|
// If our local pending block tip is not in the chain on L1 a "prune" must have happened
|
|
700
737
|
// or the L1 have reorged.
|
|
701
738
|
// In any case, we have to figure out how far into the past the action will take us.
|
|
702
739
|
// For simplicity here, we will simply rewind until we end in a block that is also on the chain on L1.
|
|
703
|
-
this.log.debug(
|
|
740
|
+
this.log.debug(
|
|
741
|
+
`L2 prune has been detected due to local pending block ${localPendingBlockNumber} not in chain`,
|
|
742
|
+
{ localPendingBlockNumber, localPendingArchiveRoot, archiveForLocalPendingBlockNumber },
|
|
743
|
+
);
|
|
704
744
|
|
|
705
745
|
let tipAfterUnwind = localPendingBlockNumber;
|
|
706
746
|
while (true) {
|
|
@@ -762,8 +802,36 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
762
802
|
);
|
|
763
803
|
|
|
764
804
|
const publishedBlocks = retrievedBlocks.map(b => retrievedBlockToPublishedL2Block(b));
|
|
805
|
+
const validBlocks: PublishedL2Block[] = [];
|
|
765
806
|
|
|
766
807
|
for (const block of publishedBlocks) {
|
|
808
|
+
const validationResult = await validateBlockAttestations(block, this.epochCache, this.l1constants, this.log);
|
|
809
|
+
|
|
810
|
+
// Only update the validation result if it has changed, so we can keep track of the first invalid block
|
|
811
|
+
// in case there is a sequence of more than one invalid block, as we need to invalidate the first one.
|
|
812
|
+
if (rollupStatus.validationResult?.valid !== validationResult.valid) {
|
|
813
|
+
rollupStatus.validationResult = validationResult;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
if (!validationResult.valid) {
|
|
817
|
+
this.log.warn(`Skipping block ${block.block.number} due to invalid attestations`, {
|
|
818
|
+
blockHash: block.block.hash(),
|
|
819
|
+
l1BlockNumber: block.l1.blockNumber,
|
|
820
|
+
...pick(validationResult, 'reason'),
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Emit event for invalid block detection
|
|
824
|
+
this.emit(L2BlockSourceEvents.InvalidAttestationsBlockDetected, {
|
|
825
|
+
type: L2BlockSourceEvents.InvalidAttestationsBlockDetected,
|
|
826
|
+
validationResult,
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// We keep consuming blocks if we find an invalid one, since we do not listen for BlockInvalidated events
|
|
830
|
+
// We just pretend the invalid ones are not there and keep consuming the next blocks
|
|
831
|
+
continue;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
validBlocks.push(block);
|
|
767
835
|
this.log.debug(`Ingesting new L2 block ${block.block.number} with ${block.block.body.txEffects.length} txs`, {
|
|
768
836
|
blockHash: block.block.hash(),
|
|
769
837
|
l1BlockNumber: block.l1.blockNumber,
|
|
@@ -773,10 +841,10 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
773
841
|
}
|
|
774
842
|
|
|
775
843
|
try {
|
|
776
|
-
const [processDuration] = await elapsed(() => this.store.addBlocks(
|
|
844
|
+
const [processDuration] = await elapsed(() => this.store.addBlocks(validBlocks));
|
|
777
845
|
this.instrumentation.processNewBlocks(
|
|
778
|
-
processDuration /
|
|
779
|
-
|
|
846
|
+
processDuration / validBlocks.length,
|
|
847
|
+
validBlocks.map(b => b.block),
|
|
780
848
|
);
|
|
781
849
|
} catch (err) {
|
|
782
850
|
if (err instanceof InitialBlockNumberNotSequentialError) {
|
|
@@ -799,7 +867,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
799
867
|
throw err;
|
|
800
868
|
}
|
|
801
869
|
|
|
802
|
-
for (const block of
|
|
870
|
+
for (const block of validBlocks) {
|
|
803
871
|
this.log.info(`Downloaded L2 block ${block.block.number}`, {
|
|
804
872
|
blockHash: await block.block.hash(),
|
|
805
873
|
blockNumber: block.block.number,
|
|
@@ -809,7 +877,7 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
809
877
|
archiveNextLeafIndex: block.block.archive.nextAvailableLeafIndex,
|
|
810
878
|
});
|
|
811
879
|
}
|
|
812
|
-
lastRetrievedBlock =
|
|
880
|
+
lastRetrievedBlock = validBlocks.at(-1) ?? lastRetrievedBlock;
|
|
813
881
|
} while (searchEndBlock < currentL1BlockNumber);
|
|
814
882
|
|
|
815
883
|
// Important that we update AFTER inserting the blocks.
|
|
@@ -1160,6 +1228,14 @@ export class Archiver extends (EventEmitter as new () => ArchiverEmitter) implem
|
|
|
1160
1228
|
return this.store.getDebugFunctionName(address, selector);
|
|
1161
1229
|
}
|
|
1162
1230
|
|
|
1231
|
+
getPendingChainValidationStatus(): Promise<ValidateBlockResult> {
|
|
1232
|
+
return Promise.resolve(this.pendingChainValidationStatus);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
isPendingChainInvalid(): Promise<boolean> {
|
|
1236
|
+
return Promise.resolve(this.pendingChainValidationStatus.valid === false);
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1163
1239
|
async getL2Tips(): Promise<L2Tips> {
|
|
1164
1240
|
const [latestBlockNumber, provenBlockNumber] = await Promise.all([
|
|
1165
1241
|
this.getBlockNumber(),
|
|
@@ -1282,15 +1358,15 @@ export class ArchiverStoreHelper
|
|
|
1282
1358
|
constructor(protected readonly store: ArchiverDataStore) {}
|
|
1283
1359
|
|
|
1284
1360
|
/**
|
|
1285
|
-
* Extracts and stores contract classes out of
|
|
1361
|
+
* Extracts and stores contract classes out of ContractClassPublished events emitted by the class registry contract.
|
|
1286
1362
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
1287
1363
|
*/
|
|
1288
|
-
async #
|
|
1289
|
-
const
|
|
1290
|
-
.filter(log =>
|
|
1291
|
-
.map(log =>
|
|
1364
|
+
async #updatePublishedContractClasses(allLogs: ContractClassLog[], blockNum: number, operation: Operation) {
|
|
1365
|
+
const contractClassPublishedEvents = allLogs
|
|
1366
|
+
.filter(log => ContractClassPublishedEvent.isContractClassPublishedEvent(log))
|
|
1367
|
+
.map(log => ContractClassPublishedEvent.fromLog(log));
|
|
1292
1368
|
|
|
1293
|
-
const contractClasses = await Promise.all(
|
|
1369
|
+
const contractClasses = await Promise.all(contractClassPublishedEvents.map(e => e.toContractClassPublic()));
|
|
1294
1370
|
if (contractClasses.length > 0) {
|
|
1295
1371
|
contractClasses.forEach(c => this.#log.verbose(`${Operation[operation]} contract class ${c.id.toString()}`));
|
|
1296
1372
|
if (operation == Operation.Store) {
|
|
@@ -1307,13 +1383,13 @@ export class ArchiverStoreHelper
|
|
|
1307
1383
|
}
|
|
1308
1384
|
|
|
1309
1385
|
/**
|
|
1310
|
-
* Extracts and stores contract instances out of
|
|
1386
|
+
* Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
|
|
1311
1387
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
1312
1388
|
*/
|
|
1313
1389
|
async #updateDeployedContractInstances(allLogs: PrivateLog[], blockNum: number, operation: Operation) {
|
|
1314
1390
|
const contractInstances = allLogs
|
|
1315
|
-
.filter(log =>
|
|
1316
|
-
.map(log =>
|
|
1391
|
+
.filter(log => ContractInstancePublishedEvent.isContractInstancePublishedEvent(log))
|
|
1392
|
+
.map(log => ContractInstancePublishedEvent.fromLog(log))
|
|
1317
1393
|
.map(e => e.toContractInstance());
|
|
1318
1394
|
if (contractInstances.length > 0) {
|
|
1319
1395
|
contractInstances.forEach(c =>
|
|
@@ -1329,7 +1405,7 @@ export class ArchiverStoreHelper
|
|
|
1329
1405
|
}
|
|
1330
1406
|
|
|
1331
1407
|
/**
|
|
1332
|
-
* Extracts and stores contract instances out of
|
|
1408
|
+
* Extracts and stores contract instances out of ContractInstancePublished events emitted by the canonical deployer contract.
|
|
1333
1409
|
* @param allLogs - All logs emitted in a bunch of blocks.
|
|
1334
1410
|
* @param timestamp - Timestamp at which the updates were scheduled.
|
|
1335
1411
|
* @param operation - The operation to perform on the contract instance updates (Store or Delete).
|
|
@@ -1428,12 +1504,12 @@ export class ArchiverStoreHelper
|
|
|
1428
1504
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
1429
1505
|
...blocks.map(async block => {
|
|
1430
1506
|
const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
|
|
1431
|
-
//
|
|
1507
|
+
// ContractInstancePublished event logs are broadcast in privateLogs.
|
|
1432
1508
|
const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
|
|
1433
1509
|
const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
|
|
1434
1510
|
return (
|
|
1435
1511
|
await Promise.all([
|
|
1436
|
-
this.#
|
|
1512
|
+
this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Store),
|
|
1437
1513
|
this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Store),
|
|
1438
1514
|
this.#updateUpdatedContractInstances(
|
|
1439
1515
|
publicLogs,
|
|
@@ -1466,13 +1542,13 @@ export class ArchiverStoreHelper
|
|
|
1466
1542
|
// Unroll all logs emitted during the retrieved blocks and extract any contract classes and instances from them
|
|
1467
1543
|
...blocks.map(async block => {
|
|
1468
1544
|
const contractClassLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.contractClassLogs);
|
|
1469
|
-
//
|
|
1545
|
+
// ContractInstancePublished event logs are broadcast in privateLogs.
|
|
1470
1546
|
const privateLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.privateLogs);
|
|
1471
1547
|
const publicLogs = block.block.body.txEffects.flatMap(txEffect => txEffect.publicLogs);
|
|
1472
1548
|
|
|
1473
1549
|
return (
|
|
1474
1550
|
await Promise.all([
|
|
1475
|
-
this.#
|
|
1551
|
+
this.#updatePublishedContractClasses(contractClassLogs, block.block.number, Operation.Delete),
|
|
1476
1552
|
this.#updateDeployedContractInstances(privateLogs, block.block.number, Operation.Delete),
|
|
1477
1553
|
this.#updateUpdatedContractInstances(
|
|
1478
1554
|
publicLogs,
|
|
@@ -311,7 +311,7 @@ async function getBlockFromRollupTx(
|
|
|
311
311
|
throw new Error(`Unexpected rollup method called ${rollupFunctionName}`);
|
|
312
312
|
}
|
|
313
313
|
|
|
314
|
-
const [decodedArgs,
|
|
314
|
+
const [decodedArgs, packedAttestations, _signers, _blobInput] = rollupArgs! as readonly [
|
|
315
315
|
{
|
|
316
316
|
archive: Hex;
|
|
317
317
|
stateReference: ViemStateReference;
|
|
@@ -322,9 +322,23 @@ async function getBlockFromRollupTx(
|
|
|
322
322
|
txHashes: readonly Hex[];
|
|
323
323
|
},
|
|
324
324
|
ViemCommitteeAttestations,
|
|
325
|
+
Hex[],
|
|
325
326
|
Hex,
|
|
326
327
|
];
|
|
327
328
|
|
|
329
|
+
const attestations = CommitteeAttestation.fromPacked(packedAttestations, targetCommitteeSize);
|
|
330
|
+
|
|
331
|
+
logger.trace(`Recovered propose calldata from tx ${txHash}`, {
|
|
332
|
+
l2BlockNumber,
|
|
333
|
+
archive: decodedArgs.archive,
|
|
334
|
+
stateReference: decodedArgs.stateReference,
|
|
335
|
+
header: decodedArgs.header,
|
|
336
|
+
blobHashes,
|
|
337
|
+
attestations,
|
|
338
|
+
packedAttestations,
|
|
339
|
+
targetCommitteeSize,
|
|
340
|
+
});
|
|
341
|
+
|
|
328
342
|
// TODO(md): why is the proposed block header different to the actual block header?
|
|
329
343
|
// This is likely going to be a footgun
|
|
330
344
|
const header = ProposedBlockHeader.fromViem(decodedArgs.header);
|
|
@@ -358,7 +372,7 @@ async function getBlockFromRollupTx(
|
|
|
358
372
|
stateReference,
|
|
359
373
|
header,
|
|
360
374
|
body,
|
|
361
|
-
attestations
|
|
375
|
+
attestations,
|
|
362
376
|
};
|
|
363
377
|
}
|
|
364
378
|
|
|
@@ -16,12 +16,12 @@ type ContractInstanceUpdateKey = [string, string] | [string, string, number];
|
|
|
16
16
|
*/
|
|
17
17
|
export class ContractInstanceStore {
|
|
18
18
|
#contractInstances: AztecAsyncMap<string, Buffer>;
|
|
19
|
-
#
|
|
19
|
+
#contractInstancePublishedAt: AztecAsyncMap<string, number>;
|
|
20
20
|
#contractInstanceUpdates: AztecAsyncMap<ContractInstanceUpdateKey, Buffer>;
|
|
21
21
|
|
|
22
22
|
constructor(private db: AztecAsyncKVStore) {
|
|
23
23
|
this.#contractInstances = db.openMap('archiver_contract_instances');
|
|
24
|
-
this.#
|
|
24
|
+
this.#contractInstancePublishedAt = db.openMap('archiver_contract_instances_publication_block_number');
|
|
25
25
|
this.#contractInstanceUpdates = db.openMap('archiver_contract_instance_updates');
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -31,14 +31,14 @@ export class ContractInstanceStore {
|
|
|
31
31
|
contractInstance.address.toString(),
|
|
32
32
|
new SerializableContractInstance(contractInstance).toBuffer(),
|
|
33
33
|
);
|
|
34
|
-
await this.#
|
|
34
|
+
await this.#contractInstancePublishedAt.set(contractInstance.address.toString(), blockNumber);
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
39
39
|
return this.db.transactionAsync(async () => {
|
|
40
40
|
await this.#contractInstances.delete(contractInstance.address.toString());
|
|
41
|
-
await this.#
|
|
41
|
+
await this.#contractInstancePublishedAt.delete(contractInstance.address.toString());
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -110,6 +110,6 @@ export class ContractInstanceStore {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
getContractInstanceDeploymentBlockNumber(address: AztecAddress): Promise<number | undefined> {
|
|
113
|
-
return this.#
|
|
113
|
+
return this.#contractInstancePublishedAt.getAsync(address.toString());
|
|
114
114
|
}
|
|
115
115
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
import {
|
|
4
|
+
type PublishedL2Block,
|
|
5
|
+
type ValidateBlockResult,
|
|
6
|
+
getAttestationsFromPublishedL2Block,
|
|
7
|
+
} from '@aztec/stdlib/block';
|
|
8
|
+
import { type L1RollupConstants, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
9
|
+
|
|
10
|
+
export type { ValidateBlockResult };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates the attestations submitted for the given block.
|
|
14
|
+
* Returns true if the attestations are valid and sufficient, false otherwise.
|
|
15
|
+
*/
|
|
16
|
+
export async function validateBlockAttestations(
|
|
17
|
+
publishedBlock: PublishedL2Block,
|
|
18
|
+
epochCache: EpochCache,
|
|
19
|
+
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
20
|
+
logger?: Logger,
|
|
21
|
+
): Promise<ValidateBlockResult> {
|
|
22
|
+
const attestations = getAttestationsFromPublishedL2Block(publishedBlock);
|
|
23
|
+
const { block } = publishedBlock;
|
|
24
|
+
const blockHash = await block.hash().then(hash => hash.toString());
|
|
25
|
+
const archiveRoot = block.archive.root.toString();
|
|
26
|
+
const slot = block.header.getSlot();
|
|
27
|
+
const epoch = getEpochAtSlot(slot, constants);
|
|
28
|
+
const { committee, seed } = await epochCache.getCommitteeForEpoch(epoch);
|
|
29
|
+
const logData = { blockNumber: block.number, slot, epoch, blockHash, archiveRoot };
|
|
30
|
+
|
|
31
|
+
logger?.debug(`Validating attestations for block ${block.number} at slot ${slot} in epoch ${epoch}`, {
|
|
32
|
+
committee: (committee ?? []).map(member => member.toString()),
|
|
33
|
+
recoveredAttestors: attestations.map(a => a.getSender().toString()),
|
|
34
|
+
postedAttestations: publishedBlock.attestations.map(a =>
|
|
35
|
+
a.address.isZero() ? a.signature.toString() : a.address.toString(),
|
|
36
|
+
),
|
|
37
|
+
...logData,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!committee || committee.length === 0) {
|
|
41
|
+
logger?.warn(`No committee found for epoch ${epoch} at slot ${slot}. Accepting block without validation.`, logData);
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const committeeSet = new Set(committee.map(member => member.toString()));
|
|
46
|
+
const requiredAttestationCount = Math.floor((committee.length * 2) / 3) + 1;
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < attestations.length; i++) {
|
|
49
|
+
const attestation = attestations[i];
|
|
50
|
+
const signer = attestation.getSender().toString();
|
|
51
|
+
if (!committeeSet.has(signer)) {
|
|
52
|
+
logger?.warn(`Attestation from non-committee member ${signer} at slot ${slot}`, { committee });
|
|
53
|
+
const reason = 'invalid-attestation';
|
|
54
|
+
return { valid: false, reason, invalidIndex: i, block: publishedBlock, committee, seed, epoch, attestations };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (attestations.length < requiredAttestationCount) {
|
|
59
|
+
logger?.warn(`Insufficient attestations for block at slot ${slot}`, {
|
|
60
|
+
requiredAttestations: requiredAttestationCount,
|
|
61
|
+
actualAttestations: attestations.length,
|
|
62
|
+
...logData,
|
|
63
|
+
});
|
|
64
|
+
const reason = 'insufficient-attestations';
|
|
65
|
+
return { valid: false, reason, block: publishedBlock, committee, seed, epoch, attestations };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logger?.debug(`Block attestations validated successfully for block ${block.number} at slot ${slot}`, logData);
|
|
69
|
+
return { valid: true };
|
|
70
|
+
}
|
package/src/factory.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
|
|
2
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
2
|
import type { DataStoreConfig } from '@aztec/kv-store/config';
|
|
4
3
|
import { createStore } from '@aztec/kv-store/lmdb-v2';
|
|
@@ -10,9 +9,8 @@ import type { L2BlockSourceEventEmitter } from '@aztec/stdlib/block';
|
|
|
10
9
|
import { type ContractClassPublic, computePublicBytecodeCommitment } from '@aztec/stdlib/contract';
|
|
11
10
|
import type { ArchiverApi, Service } from '@aztec/stdlib/interfaces/server';
|
|
12
11
|
import { getComponentsVersionsFromConfig } from '@aztec/stdlib/versioning';
|
|
13
|
-
import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client';
|
|
14
12
|
|
|
15
|
-
import { Archiver } from './archiver/archiver.js';
|
|
13
|
+
import { Archiver, type ArchiverDeps } from './archiver/archiver.js';
|
|
16
14
|
import type { ArchiverConfig } from './archiver/config.js';
|
|
17
15
|
import { ARCHIVER_DB_VERSION, KVArchiverDataStore } from './archiver/kv_archiver_store/kv_archiver_store.js';
|
|
18
16
|
import { createArchiverClient } from './rpc/index.js';
|
|
@@ -41,13 +39,12 @@ export async function createArchiverStore(
|
|
|
41
39
|
*/
|
|
42
40
|
export async function createArchiver(
|
|
43
41
|
config: ArchiverConfig & DataStoreConfig,
|
|
44
|
-
|
|
42
|
+
deps: ArchiverDeps,
|
|
45
43
|
opts: { blockUntilSync: boolean } = { blockUntilSync: true },
|
|
46
|
-
telemetry: TelemetryClient = getTelemetryClient(),
|
|
47
44
|
): Promise<ArchiverApi & Service & L2BlockSourceEventEmitter> {
|
|
48
45
|
const archiverStore = await createArchiverStore(config);
|
|
49
46
|
await registerProtocolContracts(archiverStore);
|
|
50
|
-
return Archiver.createAndSync(config, archiverStore,
|
|
47
|
+
return Archiver.createAndSync(config, archiverStore, deps, opts.blockUntilSync);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|