@aztec-labs/archiver 6.0.0-rc.1 → 6.0.0-rc.3
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/README.md +7 -1
- package/dest/errors.d.ts +20 -1
- package/dest/errors.d.ts.map +1 -1
- package/dest/errors.js +22 -0
- package/dest/l1/bin/retrieve-calldata.js +14 -4
- package/dest/l1/calldata_retriever.d.ts +15 -17
- package/dest/l1/calldata_retriever.d.ts.map +1 -1
- package/dest/l1/calldata_retriever.js +2 -9
- package/dest/l1/data_retrieval.d.ts +7 -3
- package/dest/l1/data_retrieval.d.ts.map +1 -1
- package/dest/l1/data_retrieval.js +14 -9
- package/dest/modules/data_store_updater.js +5 -5
- package/dest/modules/l1_synchronizer.d.ts +1 -1
- package/dest/modules/l1_synchronizer.d.ts.map +1 -1
- package/dest/modules/l1_synchronizer.js +228 -191
- package/dest/modules/validation.d.ts +14 -8
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +72 -22
- package/dest/store/block_store.d.ts +2 -1
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +34 -14
- 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 +4 -2
- package/dest/store/contract_instance_store.d.ts +5 -4
- package/dest/store/contract_instance_store.d.ts.map +1 -1
- package/dest/store/contract_instance_store.js +20 -9
- package/dest/test/fake_l1_state.d.ts +3 -1
- package/dest/test/fake_l1_state.d.ts.map +1 -1
- package/dest/test/fake_l1_state.js +4 -4
- package/package.json +13 -13
- package/src/errors.ts +32 -0
- package/src/l1/bin/retrieve-calldata.ts +15 -4
- package/src/l1/calldata_retriever.ts +14 -20
- package/src/l1/data_retrieval.ts +22 -14
- package/src/modules/data_store_updater.ts +4 -4
- package/src/modules/l1_synchronizer.ts +294 -261
- package/src/modules/validation.ts +87 -28
- package/src/store/block_store.ts +41 -13
- package/src/store/contract_class_store.ts +4 -4
- package/src/store/contract_instance_store.ts +24 -17
- package/src/test/fake_l1_state.ts +9 -3
|
@@ -391,7 +391,7 @@ import { InitialCheckpointNumberNotSequentialError } from '../errors.js';
|
|
|
391
391
|
import { getCheckpointBlobDataFromBlobs, retrieveCheckpointCalldataFromRollup, retrievedToPublishedCheckpoint } from '../l1/data_retrieval.js';
|
|
392
392
|
import { ArchiverDataStoreUpdater, blockLeafCount } from './data_store_updater.js';
|
|
393
393
|
import { InboxMessageSynchronizer } from './inbox_message_synchronizer.js';
|
|
394
|
-
import {
|
|
394
|
+
import { resolveCheckpointAttestationsFromCalldata } from './validation.js';
|
|
395
395
|
/** Bounded Inbox message sync passes (a recovery step each) taken within one archiver sync iteration. */ const MAX_MESSAGE_SYNC_PASSES_PER_ITERATION = 3;
|
|
396
396
|
_dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpochPrune'), _dec2 = trackSpan('Archiver.syncL1ToL2Messages'), _dec3 = trackSpan('Archiver.handleCheckpoints');
|
|
397
397
|
/**
|
|
@@ -1059,209 +1059,246 @@ _dec = trackSpan('Archiver.syncFromL1'), _dec1 = trackSpan('Archiver.handleEpoch
|
|
|
1059
1059
|
searchStartBlock,
|
|
1060
1060
|
searchEndBlock
|
|
1061
1061
|
});
|
|
1062
|
-
//
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
} : await validateCheckpointAttestationsFromCalldata(calldataCheckpoint, this.epochCache, this.l1Constants, this.getSignatureContext(), this.log);
|
|
1082
|
-
// Also skip the checkpoint if it builds on a previously-rejected ancestor. Without
|
|
1083
|
-
// this, addCheckpoints would throw InitialCheckpointNumberNotSequentialError when the
|
|
1084
|
-
// ancestor was skipped earlier (e.g. due to invalid attestations), the catch handler
|
|
1085
|
-
// would roll back the L1 sync point, and the next iteration would re-fetch and re-throw.
|
|
1086
|
-
const rejectedAncestor = await this.stores.blocks.getRejectedCheckpointByArchiveRoot(calldataCheckpoint.header.lastArchiveRoot);
|
|
1087
|
-
// Update the validation result if it has changed, so we can keep track of the first invalid checkpoint
|
|
1088
|
-
// in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
|
|
1089
|
-
// There is an exception though: if a checkpoint is invalidated and replaced with another invalid checkpoint,
|
|
1090
|
-
// we need to update the validation result, since we need to be able to invalidate the new one.
|
|
1091
|
-
// See test 'chain progresses if an invalid checkpoint is invalidated with an invalid one' for more info.
|
|
1092
|
-
// Do not update the validation result if there is a rejected ancestor, since in that case we want to keep the
|
|
1093
|
-
// original invalidation, as the new checkpoint is extending from a previous invalid one.
|
|
1094
|
-
const validStatusChanged = rollupStatus.validationResult?.valid !== validationResult.valid;
|
|
1095
|
-
const invalidStatusWithSameCheckpointNumber = !validationResult.valid && rollupStatus.validationResult && !rollupStatus.validationResult.valid && rollupStatus.validationResult.checkpoint.checkpointNumber === validationResult.checkpoint.checkpointNumber;
|
|
1096
|
-
if (!rejectedAncestor && (validStatusChanged || invalidStatusWithSameCheckpointNumber)) {
|
|
1097
|
-
rollupStatus.validationResult = validationResult;
|
|
1098
|
-
}
|
|
1099
|
-
if (!validationResult.valid) {
|
|
1100
|
-
this.log.warn(`Skipping checkpoint ${calldataCheckpoint.checkpointNumber} due to invalid attestations`, {
|
|
1101
|
-
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1102
|
-
l1BlockNumber: calldataCheckpoint.l1.blockNumber,
|
|
1103
|
-
...pick(validationResult, 'reason')
|
|
1104
|
-
});
|
|
1105
|
-
// Emit event for invalid checkpoint detection
|
|
1106
|
-
this.events.emit(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, {
|
|
1107
|
-
type: L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
|
|
1062
|
+
// Screening below records rejected checkpoints as it goes, and each such write advances the L1 sync point past
|
|
1063
|
+
// the rejected checkpoint's L1 block before the batch's valid checkpoints are persisted. If anything in the batch
|
|
1064
|
+
// throws before the batch is persisted, the sync point is restored to where it stood before the batch, so the
|
|
1065
|
+
// next iteration processes the whole batch again instead of skipping its valid checkpoints. Once the batch is
|
|
1066
|
+
// persisted the sync point is left alone: replaying it would refetch blobs for checkpoints already stored,
|
|
1067
|
+
// including promoted ones this node never fetched blobs for, and stall if those blobs are unavailable.
|
|
1068
|
+
const syncPointBeforeBatch = await this.stores.blocks.getSynchedL1BlockNumber();
|
|
1069
|
+
let batchPersisted = false;
|
|
1070
|
+
try {
|
|
1071
|
+
// Decode and validate attestations from CALLDATA before fetching any blobs, so a checkpoint with invalid
|
|
1072
|
+
// attestations is rejected without decoding a possibly malformed blob. Runs even when validation is
|
|
1073
|
+
// disabled, since promotion and persistence need the decoded attestations.
|
|
1074
|
+
const resolvedCheckpoints = await asyncPool(10, calldataCheckpoints, async (calldataCheckpoint)=>{
|
|
1075
|
+
const { attestations, validationResult } = await resolveCheckpointAttestationsFromCalldata(calldataCheckpoint, this.epochCache, this.l1Constants, this.getSignatureContext(), this.log);
|
|
1076
|
+
return {
|
|
1077
|
+
checkpoint: {
|
|
1078
|
+
...calldataCheckpoint,
|
|
1079
|
+
attestations
|
|
1080
|
+
},
|
|
1108
1081
|
validationResult
|
|
1109
|
-
});
|
|
1110
|
-
// Persist a rejected-ancestor entry so any later checkpoint that builds on this one
|
|
1111
|
-
// is detected and skipped (rather than tripping the addCheckpoints consecutive-number
|
|
1112
|
-
// check and causing the sync point to roll back in a loop).
|
|
1113
|
-
await this.stores.blocks.addRejectedCheckpoint({
|
|
1114
|
-
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1115
|
-
archiveRoot: calldataCheckpoint.archiveRoot,
|
|
1116
|
-
parentArchiveRoot: calldataCheckpoint.header.lastArchiveRoot,
|
|
1117
|
-
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1118
|
-
l1: calldataCheckpoint.l1,
|
|
1119
|
-
reason: 'invalid-attestations'
|
|
1120
|
-
});
|
|
1121
|
-
continue;
|
|
1122
|
-
}
|
|
1123
|
-
if (rejectedAncestor) {
|
|
1124
|
-
const descendantInfo = {
|
|
1125
|
-
archive: calldataCheckpoint.archiveRoot,
|
|
1126
|
-
lastArchive: calldataCheckpoint.header.lastArchiveRoot,
|
|
1127
|
-
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1128
|
-
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1129
|
-
timestamp: calldataCheckpoint.header.timestamp
|
|
1130
1082
|
};
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1083
|
+
});
|
|
1084
|
+
// Check if the last checkpoint matches a local pending entry (so we can skip blob fetch).
|
|
1085
|
+
// We only check the last one; if it matches, the blob fetch is skipped for that entry.
|
|
1086
|
+
// TODO(palla/pipelining): We may have more than a single checkpoint to promote
|
|
1087
|
+
const lastCalldataCheckpoint = resolvedCheckpoints[resolvedCheckpoints.length - 1].checkpoint;
|
|
1088
|
+
const promoteResult = await this.tryBuildPublishedCheckpointFromProposed(lastCalldataCheckpoint);
|
|
1089
|
+
const checkpointToPromote = promoteResult && !('diverged' in promoteResult) ? promoteResult : undefined;
|
|
1090
|
+
const evictProposedFrom = promoteResult && 'diverged' in promoteResult ? promoteResult.fromCheckpointNumber : undefined;
|
|
1091
|
+
const checkpointsToIngest = [];
|
|
1092
|
+
for (const { checkpoint: calldataCheckpoint, validationResult: resolvedValidationResult } of resolvedCheckpoints){
|
|
1093
|
+
// Check the attestations uploaded by the publisher to L1 are correct.
|
|
1094
|
+
// Rollup contract does not validate attestations to save on gas, so this
|
|
1095
|
+
// falls on the nodes to verify offchain and skip those checkpoints.
|
|
1096
|
+
const validationResult = this.config.skipValidateCheckpointAttestations ? {
|
|
1097
|
+
valid: true
|
|
1098
|
+
} : resolvedValidationResult;
|
|
1099
|
+
// Also skip the checkpoint if it builds on a previously-rejected ancestor. Without
|
|
1100
|
+
// this, addCheckpoints would throw InitialCheckpointNumberNotSequentialError when the
|
|
1101
|
+
// ancestor was skipped earlier (e.g. due to invalid attestations), the catch handler
|
|
1102
|
+
// would roll back the L1 sync point, and the next iteration would re-fetch and re-throw.
|
|
1103
|
+
const rejectedAncestor = await this.stores.blocks.getRejectedCheckpointByArchiveRoot(calldataCheckpoint.header.lastArchiveRoot);
|
|
1104
|
+
// Update the validation result if it has changed, so we can keep track of the first invalid checkpoint
|
|
1105
|
+
// in case there is a sequence of more than one invalid checkpoint, as we need to invalidate the first one.
|
|
1106
|
+
// There is an exception though: if a checkpoint is invalidated and replaced with another invalid checkpoint,
|
|
1107
|
+
// we need to update the validation result, since we need to be able to invalidate the new one.
|
|
1108
|
+
// See test 'chain progresses if an invalid checkpoint is invalidated with an invalid one' for more info.
|
|
1109
|
+
// Do not update the validation result if there is a rejected ancestor, since in that case we want to keep the
|
|
1110
|
+
// original invalidation, as the new checkpoint is extending from a previous invalid one.
|
|
1111
|
+
const validStatusChanged = rollupStatus.validationResult?.valid !== validationResult.valid;
|
|
1112
|
+
const invalidStatusWithSameCheckpointNumber = !validationResult.valid && rollupStatus.validationResult && !rollupStatus.validationResult.valid && rollupStatus.validationResult.checkpoint.checkpointNumber === validationResult.checkpoint.checkpointNumber;
|
|
1113
|
+
if (!rejectedAncestor && (validStatusChanged || invalidStatusWithSameCheckpointNumber)) {
|
|
1114
|
+
rollupStatus.validationResult = validationResult;
|
|
1115
|
+
}
|
|
1116
|
+
if (!validationResult.valid) {
|
|
1117
|
+
this.log.warn(`Skipping checkpoint ${calldataCheckpoint.checkpointNumber} due to invalid attestations`, {
|
|
1118
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1119
|
+
l1BlockNumber: calldataCheckpoint.l1.blockNumber,
|
|
1120
|
+
...pick(validationResult, 'reason')
|
|
1121
|
+
});
|
|
1122
|
+
// Emit event for invalid checkpoint detection
|
|
1123
|
+
this.events.emit(L2BlockSourceEvents.InvalidAttestationsCheckpointDetected, {
|
|
1124
|
+
type: L2BlockSourceEvents.InvalidAttestationsCheckpointDetected,
|
|
1125
|
+
validationResult
|
|
1126
|
+
});
|
|
1127
|
+
// Persist a rejected-ancestor entry so any later checkpoint that builds on this one
|
|
1128
|
+
// is detected and skipped (rather than tripping the addCheckpoints consecutive-number
|
|
1129
|
+
// check and causing the sync point to roll back in a loop).
|
|
1130
|
+
await this.stores.blocks.addRejectedCheckpoint({
|
|
1131
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1132
|
+
archiveRoot: calldataCheckpoint.archiveRoot,
|
|
1133
|
+
parentArchiveRoot: calldataCheckpoint.header.lastArchiveRoot,
|
|
1134
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1135
|
+
l1: calldataCheckpoint.l1,
|
|
1136
|
+
reason: 'invalid-attestations'
|
|
1137
|
+
});
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
if (rejectedAncestor) {
|
|
1141
|
+
const descendantInfo = {
|
|
1142
|
+
archive: calldataCheckpoint.archiveRoot,
|
|
1143
|
+
lastArchive: calldataCheckpoint.header.lastArchiveRoot,
|
|
1144
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1145
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1146
|
+
timestamp: calldataCheckpoint.header.timestamp
|
|
1147
|
+
};
|
|
1148
|
+
this.log.warn(`Skipping checkpoint ${calldataCheckpoint.checkpointNumber} as it is a descendant of ` + `rejected checkpoint ${rejectedAncestor.checkpointNumber} (${rejectedAncestor.reason})`, {
|
|
1149
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1150
|
+
l1BlockNumber: calldataCheckpoint.l1.blockNumber,
|
|
1151
|
+
l1BlockHash: calldataCheckpoint.l1.blockHash,
|
|
1152
|
+
ancestorCheckpointNumber: rejectedAncestor.checkpointNumber,
|
|
1153
|
+
ancestorArchiveRoot: rejectedAncestor.archiveRoot.toString(),
|
|
1154
|
+
ancestorReason: rejectedAncestor.reason
|
|
1155
|
+
});
|
|
1156
|
+
this.events.emit(L2BlockSourceEvents.DescendentOfInvalidAttestationsCheckpointDetected, {
|
|
1157
|
+
type: L2BlockSourceEvents.DescendentOfInvalidAttestationsCheckpointDetected,
|
|
1158
|
+
checkpoint: descendantInfo,
|
|
1159
|
+
ancestorArchiveRoot: rejectedAncestor.archiveRoot,
|
|
1160
|
+
ancestorCheckpointNumber: rejectedAncestor.checkpointNumber
|
|
1161
|
+
});
|
|
1162
|
+
// Persist this chainpoint as rejected as well, so we can construct a chain of
|
|
1163
|
+
// skipped checkpoints starting from the first one with invalid attestations.
|
|
1164
|
+
await this.stores.blocks.addRejectedCheckpoint({
|
|
1165
|
+
checkpointNumber: calldataCheckpoint.checkpointNumber,
|
|
1166
|
+
archiveRoot: calldataCheckpoint.archiveRoot,
|
|
1167
|
+
parentArchiveRoot: calldataCheckpoint.header.lastArchiveRoot,
|
|
1168
|
+
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1169
|
+
l1: calldataCheckpoint.l1,
|
|
1170
|
+
reason: 'descends-from-invalid-attestations'
|
|
1171
|
+
});
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
checkpointsToIngest.push(calldataCheckpoint);
|
|
1175
|
+
}
|
|
1176
|
+
// Fetch blobs in parallel only for the surviving (attestation-valid, non-descendant) checkpoints,
|
|
1177
|
+
// then build the full published checkpoints. The last calldata checkpoint may be promotable from a
|
|
1178
|
+
// local proposed block (checkpointToPromote), in which case it carries no blob to fetch. A missing or
|
|
1179
|
+
// undecodable blob throws and propagates, and the batch guard restores the L1 sync point so the fetch is retried.
|
|
1180
|
+
const toFetchBlobs = checkpointToPromote ? checkpointsToIngest.filter((c)=>c.checkpointNumber !== checkpointToPromote.checkpoint.number) : checkpointsToIngest;
|
|
1181
|
+
const blobFetched = await asyncPool(10, toFetchBlobs, async (checkpoint)=>retrievedToPublishedCheckpoint({
|
|
1182
|
+
...checkpoint,
|
|
1183
|
+
checkpointBlobData: await getCheckpointBlobDataFromBlobs(this.blobClient, checkpoint.l1.blockHash, checkpoint.blobHashes, checkpoint.checkpointNumber, this.log, !initialSyncComplete, checkpoint.parentBeaconBlockRoot, checkpoint.l1.timestamp)
|
|
1184
|
+
}));
|
|
1185
|
+
// Index the built checkpoints by number so we can ingest them in calldata order, slotting in the
|
|
1186
|
+
// promoted checkpoint (built from a local proposed block rather than blobs).
|
|
1187
|
+
const publishedByNumber = new Map(blobFetched.map((publishedCheckpoint)=>[
|
|
1188
|
+
publishedCheckpoint.checkpoint.number,
|
|
1189
|
+
publishedCheckpoint
|
|
1190
|
+
]));
|
|
1191
|
+
if (checkpointToPromote) {
|
|
1192
|
+
publishedByNumber.set(checkpointToPromote.checkpoint.number, checkpointToPromote);
|
|
1193
|
+
}
|
|
1194
|
+
const validCheckpoints = [];
|
|
1195
|
+
for (const calldataCheckpoint of checkpointsToIngest){
|
|
1196
|
+
const published = publishedByNumber.get(calldataCheckpoint.checkpointNumber);
|
|
1197
|
+
validCheckpoints.push(published);
|
|
1198
|
+
this.log.debug(`Ingesting new checkpoint ${published.checkpoint.number} with ${published.checkpoint.blocks.length} blocks`, {
|
|
1199
|
+
checkpointHash: published.checkpoint.hash(),
|
|
1200
|
+
l1BlockNumber: published.l1.blockNumber,
|
|
1201
|
+
...published.checkpoint.header.toInspect(),
|
|
1202
|
+
blocks: published.checkpoint.blocks.map((b)=>b.getStats())
|
|
1144
1203
|
});
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
slotNumber: calldataCheckpoint.header.slotNumber,
|
|
1152
|
-
l1: calldataCheckpoint.l1,
|
|
1153
|
-
reason: 'descends-from-invalid-attestations'
|
|
1204
|
+
}
|
|
1205
|
+
for (const published of validCheckpoints){
|
|
1206
|
+
this.instrumentation.processCheckpointL1Timing({
|
|
1207
|
+
slotNumber: published.checkpoint.header.slotNumber,
|
|
1208
|
+
l1Timestamp: published.l1.timestamp,
|
|
1209
|
+
l1Constants: this.l1Constants
|
|
1154
1210
|
});
|
|
1155
|
-
continue;
|
|
1156
1211
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1212
|
+
try {
|
|
1213
|
+
const updatedValidationResult = rollupStatus.validationResult === initialValidationResult ? undefined : rollupStatus.validationResult;
|
|
1214
|
+
// Split valid checkpoints: the promoted one (if any) is persisted via the proposed-promotion path,
|
|
1215
|
+
// the rest via addCheckpoints. Both paths run within the same store transaction for atomicity.
|
|
1216
|
+
const [[maybeValidCheckpointToPromote], checkpointsToAdd] = partition(validCheckpoints, (c)=>c.checkpoint.number === checkpointToPromote?.checkpoint.number);
|
|
1217
|
+
const [processDuration, result] = await elapsed(()=>execInSpan(this.tracer, 'Archiver.addCheckpoints', async ()=>{
|
|
1218
|
+
const addResult = await this.updater.addCheckpoints(checkpointsToAdd, updatedValidationResult, maybeValidCheckpointToPromote && {
|
|
1219
|
+
l1: lastCalldataCheckpoint.l1,
|
|
1220
|
+
attestations: lastCalldataCheckpoint.attestations,
|
|
1221
|
+
verbatimAttestations: lastCalldataCheckpoint.verbatimAttestations,
|
|
1222
|
+
checkpoint: maybeValidCheckpointToPromote
|
|
1223
|
+
}, evictProposedFrom);
|
|
1224
|
+
// Set before the span wrappers finish, so a throw while closing the span doesn't count as unpersisted.
|
|
1225
|
+
batchPersisted = true;
|
|
1226
|
+
return addResult;
|
|
1227
|
+
}));
|
|
1228
|
+
if (validCheckpoints.length > 0) {
|
|
1229
|
+
this.instrumentation.processNewCheckpointedBlocks(processDuration / validCheckpoints.length, validCheckpoints.flatMap((c)=>c.checkpoint.blocks));
|
|
1230
|
+
}
|
|
1231
|
+
// Record blocks newly fetched from L1 checkpoint payloads as added. The promoted checkpoint (if any) is
|
|
1232
|
+
// excluded: its blocks were already in local archiver storage (added via the proposed-block path) so the
|
|
1233
|
+
// block stream does not need them re-downloaded.
|
|
1234
|
+
blocksAdded.push(...checkpointsToAdd.flatMap((c)=>c.checkpoint.blocks));
|
|
1235
|
+
// If blocks were pruned due to conflict with L1 checkpoints, emit event
|
|
1236
|
+
if (result.prunedBlocks && result.prunedBlocks.length > 0) {
|
|
1237
|
+
const prunedCheckpointNumber = result.prunedBlocks[0].checkpointNumber;
|
|
1238
|
+
const prunedSlotNumber = result.prunedBlocks[0].header.globalVariables.slotNumber;
|
|
1239
|
+
this.log.info(`Pruned ${result.prunedBlocks.length} mismatching blocks for checkpoint ${prunedCheckpointNumber}`, {
|
|
1240
|
+
prunedBlocks: result.prunedBlocks.map((b)=>b.toBlockInfo()),
|
|
1241
|
+
prunedSlotNumber,
|
|
1242
|
+
prunedCheckpointNumber
|
|
1243
|
+
});
|
|
1244
|
+
this.instrumentation.recordPrune('l1_conflict');
|
|
1245
|
+
// Emit event for listening services to react to the prune.
|
|
1246
|
+
// Note: slotNumber comes from the first pruned block. If pruned blocks theoretically spanned multiple slots,
|
|
1247
|
+
// only one slot number would be reported (though in practice all blocks in a checkpoint span a single slot).
|
|
1248
|
+
this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
|
|
1249
|
+
type: L2BlockSourceEvents.L2PruneUncheckpointed,
|
|
1250
|
+
slotNumber: prunedSlotNumber,
|
|
1251
|
+
blocks: result.prunedBlocks
|
|
1252
|
+
});
|
|
1253
|
+
}
|
|
1254
|
+
} catch (err) {
|
|
1255
|
+
if (err instanceof InitialCheckpointNumberNotSequentialError) {
|
|
1256
|
+
const { previousCheckpointNumber, newCheckpointNumber } = err;
|
|
1257
|
+
const previousCheckpoint = previousCheckpointNumber ? await this.stores.blocks.getCheckpointData(CheckpointNumber(previousCheckpointNumber)) : undefined;
|
|
1258
|
+
const lastFinalizedCheckpoint = await this.stores.blocks.getCheckpointData(await this.stores.blocks.getFinalizedCheckpointNumber());
|
|
1259
|
+
const updatedL1SyncPoint = previousCheckpoint?.l1.blockNumber ?? lastFinalizedCheckpoint?.l1.blockNumber ?? this.l1Constants.l1StartBlock;
|
|
1260
|
+
await this.stores.blocks.setSynchedL1BlockNumber(updatedL1SyncPoint);
|
|
1261
|
+
this.log.warn(`Attempting to insert checkpoint ${newCheckpointNumber} with previous block ${previousCheckpointNumber}. Rolling back L1 sync point to ${updatedL1SyncPoint} to try and fetch the missing blocks.`, {
|
|
1262
|
+
previousCheckpointNumber,
|
|
1263
|
+
previousCheckpoint: previousCheckpoint?.header.toInspect(),
|
|
1264
|
+
lastFinalizedCheckpoint: lastFinalizedCheckpoint?.header.toInspect(),
|
|
1265
|
+
l1StartBlock: this.l1Constants.l1StartBlock,
|
|
1266
|
+
newCheckpointNumber,
|
|
1267
|
+
updatedL1SyncPoint
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
throw err;
|
|
1208
1271
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
prunedBlocks: result.prunedBlocks.map((b)=>b.toBlockInfo()),
|
|
1219
|
-
prunedSlotNumber,
|
|
1220
|
-
prunedCheckpointNumber
|
|
1221
|
-
});
|
|
1222
|
-
this.instrumentation.recordPrune('l1_conflict');
|
|
1223
|
-
// Emit event for listening services to react to the prune.
|
|
1224
|
-
// Note: slotNumber comes from the first pruned block. If pruned blocks theoretically spanned multiple slots,
|
|
1225
|
-
// only one slot number would be reported (though in practice all blocks in a checkpoint span a single slot).
|
|
1226
|
-
this.events.emit(L2BlockSourceEvents.L2PruneUncheckpointed, {
|
|
1227
|
-
type: L2BlockSourceEvents.L2PruneUncheckpointed,
|
|
1228
|
-
slotNumber: prunedSlotNumber,
|
|
1229
|
-
blocks: result.prunedBlocks
|
|
1272
|
+
for (const checkpoint of validCheckpoints){
|
|
1273
|
+
this.log.info(`Downloaded checkpoint ${checkpoint.checkpoint.number}`, {
|
|
1274
|
+
checkpointHash: checkpoint.checkpoint.hash(),
|
|
1275
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
1276
|
+
blockCount: checkpoint.checkpoint.blocks.length,
|
|
1277
|
+
txCount: checkpoint.checkpoint.blocks.reduce((acc, b)=>acc + b.body.txEffects.length, 0),
|
|
1278
|
+
header: checkpoint.checkpoint.header.toInspect(),
|
|
1279
|
+
archiveRoot: checkpoint.checkpoint.archive.root.toString(),
|
|
1280
|
+
archiveNextLeafIndex: checkpoint.checkpoint.archive.nextAvailableLeafIndex
|
|
1230
1281
|
});
|
|
1231
1282
|
}
|
|
1283
|
+
lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
|
|
1284
|
+
// The last checkpoint seen on L1 this batch (valid or rejected), tracked from calldata since
|
|
1285
|
+
// rejected checkpoints are no longer built into PublishedCheckpoints.
|
|
1286
|
+
lastSeenCheckpoint = lastCalldataCheckpoint;
|
|
1232
1287
|
} catch (err) {
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
lastFinalizedCheckpoint: lastFinalizedCheckpoint?.header.toInspect(),
|
|
1243
|
-
l1StartBlock: this.l1Constants.l1StartBlock,
|
|
1244
|
-
newCheckpointNumber,
|
|
1245
|
-
updatedL1SyncPoint
|
|
1288
|
+
const restoreTo = syncPointBeforeBatch ?? this.l1Constants.l1StartBlock;
|
|
1289
|
+
const currentSyncPoint = await this.stores.blocks.getSynchedL1BlockNumber();
|
|
1290
|
+
// Only ever lower it, so a deeper rewind made while handling the error (e.g. on a checkpoint gap) is kept.
|
|
1291
|
+
if (!batchPersisted && currentSyncPoint !== undefined && currentSyncPoint > restoreTo) {
|
|
1292
|
+
this.log.warn(`Restoring L1 sync point to ${restoreTo} after failing to process checkpoints`, {
|
|
1293
|
+
currentSyncPoint,
|
|
1294
|
+
restoreTo,
|
|
1295
|
+
searchStartBlock,
|
|
1296
|
+
searchEndBlock
|
|
1246
1297
|
});
|
|
1298
|
+
await this.stores.blocks.setSynchedL1BlockNumber(restoreTo);
|
|
1247
1299
|
}
|
|
1248
1300
|
throw err;
|
|
1249
1301
|
}
|
|
1250
|
-
for (const checkpoint of validCheckpoints){
|
|
1251
|
-
this.log.info(`Downloaded checkpoint ${checkpoint.checkpoint.number}`, {
|
|
1252
|
-
checkpointHash: checkpoint.checkpoint.hash(),
|
|
1253
|
-
checkpointNumber: checkpoint.checkpoint.number,
|
|
1254
|
-
blockCount: checkpoint.checkpoint.blocks.length,
|
|
1255
|
-
txCount: checkpoint.checkpoint.blocks.reduce((acc, b)=>acc + b.body.txEffects.length, 0),
|
|
1256
|
-
header: checkpoint.checkpoint.header.toInspect(),
|
|
1257
|
-
archiveRoot: checkpoint.checkpoint.archive.root.toString(),
|
|
1258
|
-
archiveNextLeafIndex: checkpoint.checkpoint.archive.nextAvailableLeafIndex
|
|
1259
|
-
});
|
|
1260
|
-
}
|
|
1261
|
-
lastRetrievedCheckpoint = validCheckpoints.at(-1) ?? lastRetrievedCheckpoint;
|
|
1262
|
-
// The last checkpoint seen on L1 this batch (valid or rejected), tracked from calldata since
|
|
1263
|
-
// rejected checkpoints are no longer built into PublishedCheckpoints.
|
|
1264
|
-
lastSeenCheckpoint = lastCalldataCheckpoint;
|
|
1265
1302
|
}while (searchEndBlock < currentL1BlockNumber)
|
|
1266
1303
|
// Important that we update AFTER inserting the blocks.
|
|
1267
1304
|
await this.updateProvenCheckpoint(rollupStatus.provenCheckpointNumber, provenArchive);
|
|
@@ -3,7 +3,7 @@ import type { ViemCommitteeAttestations } from '@aztec-labs/ethereum/contracts';
|
|
|
3
3
|
import { type CheckpointNumber } from '@aztec-labs/foundation/branded-types';
|
|
4
4
|
import type { Fr } from '@aztec-labs/foundation/curves/bn254';
|
|
5
5
|
import type { Logger } from '@aztec-labs/foundation/log';
|
|
6
|
-
import { type AttestationInfo,
|
|
6
|
+
import { type AttestationInfo, CommitteeAttestation, type ValidateCheckpointResult } from '@aztec-labs/stdlib/block';
|
|
7
7
|
import type { CheckpointInfo, PublishedCheckpoint } from '@aztec-labs/stdlib/checkpoint';
|
|
8
8
|
import { type L1RollupConstants } from '@aztec-labs/stdlib/epoch-helpers';
|
|
9
9
|
import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec-labs/stdlib/p2p';
|
|
@@ -20,21 +20,27 @@ export type CalldataCheckpointForAttestations = {
|
|
|
20
20
|
archiveRoot: Fr;
|
|
21
21
|
feeAssetPriceModifier: bigint;
|
|
22
22
|
header: CheckpointHeader;
|
|
23
|
-
attestations: CommitteeAttestation[];
|
|
24
23
|
/** The exact packed attestations tuple from L1 calldata, carried verbatim for byte-faithful invalidation. */
|
|
25
24
|
verbatimAttestations: ViemCommitteeAttestations;
|
|
26
25
|
};
|
|
26
|
+
/** A checkpoint's attestations decoded for its epoch committee, and their validation result. */
|
|
27
|
+
export type ResolvedCheckpointAttestations = {
|
|
28
|
+
/** Empty if the epoch has an open escape hatch or no committee. */
|
|
29
|
+
attestations: CommitteeAttestation[];
|
|
30
|
+
validationResult: ValidateCheckpointResult;
|
|
31
|
+
};
|
|
27
32
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
33
|
+
* Decodes and validates the attestations of a checkpoint from L1 calldata only, so an invalid checkpoint can be
|
|
34
|
+
* rejected before its blobs are fetched. The tuple is decoded against the epoch's committee size, and not at all
|
|
35
|
+
* during an escape hatch, where the proposer may post an arbitrary tuple.
|
|
36
|
+
*
|
|
37
|
+
* @throws CheckpointAttestationsDecodeError if the tuple does not decode for the epoch committee.
|
|
32
38
|
*/
|
|
33
|
-
export declare function
|
|
39
|
+
export declare function resolveCheckpointAttestationsFromCalldata(checkpoint: CalldataCheckpointForAttestations, epochCache: EpochCache, constants: Pick<L1RollupConstants, 'epochDuration'>, signatureContext: CoordinationSignatureContext, logger?: Logger): Promise<ResolvedCheckpointAttestations>;
|
|
34
40
|
/**
|
|
35
41
|
* Core attestation validation over a consensus payload, its attestations, and checkpoint metadata --
|
|
36
42
|
* independent of whether the checkpoint's blocks have been decoded from blobs. Returns true if the
|
|
37
43
|
* attestations are valid and sufficient, false otherwise.
|
|
38
44
|
*/
|
|
39
45
|
export declare function validateAttestations(payload: ConsensusPayload, attestations: CommitteeAttestation[], verbatimAttestations: ViemCommitteeAttestations, checkpointInfo: CheckpointInfo, epochCache: EpochCache, constants: Pick<L1RollupConstants, 'epochDuration'>, logger?: Logger): Promise<ValidateCheckpointResult>;
|
|
40
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
46
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGlvbi5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL21vZHVsZXMvdmFsaWRhdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUMxRCxPQUFPLEtBQUssRUFBRSx5QkFBeUIsRUFBRSxNQUFNLGdDQUFnQyxDQUFDO0FBQ2hGLE9BQU8sRUFBRSxLQUFLLGdCQUFnQixFQUFlLE1BQU0sc0NBQXNDLENBQUM7QUFFMUYsT0FBTyxLQUFLLEVBQUUsRUFBRSxFQUFFLE1BQU0scUNBQXFDLENBQUM7QUFFOUQsT0FBTyxLQUFLLEVBQUUsTUFBTSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDekQsT0FBTyxFQUNMLEtBQUssZUFBZSxFQUNwQixvQkFBb0IsRUFFcEIsS0FBSyx3QkFBd0IsRUFFOUIsTUFBTSwwQkFBMEIsQ0FBQztBQUNsQyxPQUFPLEtBQUssRUFBRSxjQUFjLEVBQUUsbUJBQW1CLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUN6RixPQUFPLEVBQUUsS0FBSyxpQkFBaUIsRUFBaUMsTUFBTSxrQ0FBa0MsQ0FBQztBQUN6RyxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsS0FBSyw0QkFBNEIsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdGLE9BQU8sS0FBSyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFJbEUsWUFBWSxFQUFFLHdCQUF3QixFQUFFLENBQUM7QUFFekM7OztHQUdHO0FBQ0gsd0JBQWdCLHlDQUF5QyxDQUN2RCxFQUFFLFVBQVUsRUFBRSxZQUFZLEVBQUUsRUFBRSxtQkFBbUIsRUFDakQsZ0JBQWdCLEVBQUUsNEJBQTRCLEdBQzdDLGVBQWUsRUFBRSxDQUduQjtBQUVELDhGQUE4RjtBQUM5RixNQUFNLE1BQU0saUNBQWlDLEdBQUc7SUFDOUMsZ0JBQWdCLEVBQUUsZ0JBQWdCLENBQUM7SUFDbkMsV0FBVyxFQUFFLEVBQUUsQ0FBQztJQUNoQixxQkFBcUIsRUFBRSxNQUFNLENBQUM7SUFDOUIsTUFBTSxFQUFFLGdCQUFnQixDQUFDO0lBQ3pCLDZHQUE2RztJQUM3RyxvQkFBb0IsRUFBRSx5QkFBeUIsQ0FBQztDQUNqRCxDQUFDO0FBRUYsZ0dBQWdHO0FBQ2hHLE1BQU0sTUFBTSw4QkFBOEIsR0FBRztJQUMzQyxtRUFBbUU7SUFDbkUsWUFBWSxFQUFFLG9CQUFvQixFQUFFLENBQUM7SUFDckMsZ0JBQWdCLEVBQUUsd0JBQXdCLENBQUM7Q0FDNUMsQ0FBQztBQUVGOzs7Ozs7R0FNRztBQUNILHdCQUFzQix5Q0FBeUMsQ0FDN0QsVUFBVSxFQUFFLGlDQUFpQyxFQUM3QyxVQUFVLEVBQUUsVUFBVSxFQUN0QixTQUFTLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixFQUFFLGVBQWUsQ0FBQyxFQUNuRCxnQkFBZ0IsRUFBRSw0QkFBNEIsRUFDOUMsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUNkLE9BQU8sQ0FBQyw4QkFBOEIsQ0FBQyxDQWtEekM7QUFFRDs7OztHQUlHO0FBQ0gsd0JBQXNCLG9CQUFvQixDQUN4QyxPQUFPLEVBQUUsZ0JBQWdCLEVBQ3pCLFlBQVksRUFBRSxvQkFBb0IsRUFBRSxFQUNwQyxvQkFBb0IsRUFBRSx5QkFBeUIsRUFDL0MsY0FBYyxFQUFFLGNBQWMsRUFDOUIsVUFBVSxFQUFFLFVBQVUsRUFDdEIsU0FBUyxFQUFFLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxlQUFlLENBQUMsRUFDbkQsTUFBTSxDQUFDLEVBQUUsTUFBTSxHQUNkLE9BQU8sQ0FBQyx3QkFBd0IsQ0FBQyxDQTJCbkMifQ==
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/modules/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,sCAAsC,CAAC;AAE1F,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/modules/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,KAAK,gBAAgB,EAAe,MAAM,sCAAsC,CAAC;AAE1F,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,qCAAqC,CAAC;AAE9D,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AACzD,OAAO,EACL,KAAK,eAAe,EACpB,oBAAoB,EAEpB,KAAK,wBAAwB,EAE9B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC;AACzF,OAAO,EAAE,KAAK,iBAAiB,EAAiC,MAAM,kCAAkC,CAAC;AACzG,OAAO,EAAE,gBAAgB,EAAE,KAAK,4BAA4B,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAIlE,YAAY,EAAE,wBAAwB,EAAE,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,yCAAyC,CACvD,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,mBAAmB,EACjD,gBAAgB,EAAE,4BAA4B,GAC7C,eAAe,EAAE,CAGnB;AAED,8FAA8F;AAC9F,MAAM,MAAM,iCAAiC,GAAG;IAC9C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,WAAW,EAAE,EAAE,CAAC;IAChB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,6GAA6G;IAC7G,oBAAoB,EAAE,yBAAyB,CAAC;CACjD,CAAC;AAEF,gGAAgG;AAChG,MAAM,MAAM,8BAA8B,GAAG;IAC3C,mEAAmE;IACnE,YAAY,EAAE,oBAAoB,EAAE,CAAC;IACrC,gBAAgB,EAAE,wBAAwB,CAAC;CAC5C,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,yCAAyC,CAC7D,UAAU,EAAE,iCAAiC,EAC7C,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACnD,gBAAgB,EAAE,4BAA4B,EAC9C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,8BAA8B,CAAC,CAkDzC;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,gBAAgB,EACzB,YAAY,EAAE,oBAAoB,EAAE,EACpC,oBAAoB,EAAE,yBAAyB,EAC/C,cAAc,EAAE,cAAc,EAC9B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,IAAI,CAAC,iBAAiB,EAAE,eAAe,CAAC,EACnD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,wBAAwB,CAAC,CA2BnC"}
|