@aztec/archiver 5.0.0-rc.1 → 5.0.0-rc.2
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 +1 -1
- package/dest/archiver.d.ts.map +1 -1
- package/dest/archiver.js +11 -5
- package/dest/l1/data_retrieval.d.ts +1 -1
- 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 +2 -2
- package/dest/modules/data_source_base.d.ts.map +1 -1
- package/dest/modules/data_source_base.js +1 -10
- 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 +62 -39
- package/dest/modules/validation.d.ts +21 -3
- package/dest/modules/validation.d.ts.map +1 -1
- package/dest/modules/validation.js +36 -11
- package/dest/store/block_store.d.ts +8 -9
- package/dest/store/block_store.d.ts.map +1 -1
- package/dest/store/block_store.js +11 -21
- 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 +12 -0
- 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 +11 -0
- 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/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 +1 -4
- package/dest/test/mock_l2_block_source.d.ts.map +1 -1
- package/dest/test/mock_l2_block_source.js +3 -22
- 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/package.json +13 -13
- package/src/archiver.ts +12 -8
- package/src/l1/data_retrieval.ts +2 -1
- package/src/modules/contract_data_source_adapter.ts +1 -10
- package/src/modules/data_source_base.ts +1 -12
- package/src/modules/l1_synchronizer.ts +91 -53
- package/src/modules/validation.ts +72 -13
- package/src/store/block_store.ts +11 -26
- package/src/store/contract_class_store.ts +12 -0
- package/src/store/contract_instance_store.ts +11 -0
- package/src/store/log_store.ts +3 -2
- package/src/store/log_store_codec.ts +11 -0
- package/src/test/mock_l1_to_l2_message_source.ts +0 -1
- package/src/test/mock_l2_block_source.ts +1 -22
- package/src/test/mock_structs.ts +5 -2
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import { EpochNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { type CheckpointNumber, EpochNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { compactArray } from '@aztec/foundation/collection';
|
|
4
|
+
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
4
5
|
import type { Logger } from '@aztec/foundation/log';
|
|
5
6
|
import {
|
|
6
7
|
type AttestationInfo,
|
|
8
|
+
type CommitteeAttestation,
|
|
7
9
|
type ValidateCheckpointNegativeResult,
|
|
8
10
|
type ValidateCheckpointResult,
|
|
9
11
|
getAttestationInfoFromPayload,
|
|
10
12
|
} from '@aztec/stdlib/block';
|
|
11
|
-
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
13
|
+
import type { CheckpointInfo, PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
12
14
|
import { type L1RollupConstants, computeQuorum, getEpochAtSlot } from '@aztec/stdlib/epoch-helpers';
|
|
13
15
|
import { ConsensusPayload, type CoordinationSignatureContext } from '@aztec/stdlib/p2p';
|
|
16
|
+
import type { CheckpointHeader } from '@aztec/stdlib/rollup';
|
|
14
17
|
|
|
15
18
|
export type { ValidateCheckpointResult };
|
|
16
19
|
|
|
@@ -27,27 +30,83 @@ export function getAttestationInfoFromPublishedCheckpoint(
|
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
|
-
* Validates the attestations
|
|
33
|
+
* Validates the attestations of a checkpoint already retrieved (with its blocks) from blobs.
|
|
31
34
|
* Returns true if the attestations are valid and sufficient, false otherwise.
|
|
32
35
|
*/
|
|
33
|
-
export
|
|
36
|
+
export function validateCheckpointAttestations(
|
|
34
37
|
publishedCheckpoint: PublishedCheckpoint,
|
|
35
38
|
epochCache: EpochCache,
|
|
36
39
|
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
37
40
|
signatureContext: CoordinationSignatureContext,
|
|
38
41
|
logger?: Logger,
|
|
39
42
|
): Promise<ValidateCheckpointResult> {
|
|
40
|
-
const attestorInfos = getAttestationInfoFromPublishedCheckpoint(publishedCheckpoint, signatureContext);
|
|
41
|
-
const attestors = compactArray(attestorInfos.map(info => ('address' in info ? info.address : undefined)));
|
|
42
43
|
const { checkpoint, attestations } = publishedCheckpoint;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
const payload = ConsensusPayload.fromCheckpoint(checkpoint, signatureContext);
|
|
45
|
+
return validateAttestations(payload, attestations, checkpoint.toCheckpointInfo(), epochCache, constants, logger);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The subset of a calldata-only checkpoint needed to validate its committee attestations. */
|
|
49
|
+
export type CalldataCheckpointForAttestations = {
|
|
50
|
+
checkpointNumber: CheckpointNumber;
|
|
51
|
+
archiveRoot: Fr;
|
|
52
|
+
feeAssetPriceModifier: bigint;
|
|
53
|
+
header: CheckpointHeader;
|
|
54
|
+
attestations: CommitteeAttestation[];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Validates the attestations of a checkpoint from L1 calldata only, without fetching or decoding its blobs.
|
|
59
|
+
* The signed consensus payload (header, archive root, fee asset price modifier) is fully available from
|
|
60
|
+
* calldata, so an invalid-attestation checkpoint can be rejected before any (possibly malformed) blob is
|
|
61
|
+
* fetched and decoded.
|
|
62
|
+
*/
|
|
63
|
+
export function validateCheckpointAttestationsFromCalldata(
|
|
64
|
+
checkpoint: CalldataCheckpointForAttestations,
|
|
65
|
+
epochCache: EpochCache,
|
|
66
|
+
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
67
|
+
signatureContext: CoordinationSignatureContext,
|
|
68
|
+
logger?: Logger,
|
|
69
|
+
): Promise<ValidateCheckpointResult> {
|
|
70
|
+
const payload = new ConsensusPayload(
|
|
71
|
+
checkpoint.header,
|
|
72
|
+
checkpoint.archiveRoot,
|
|
73
|
+
checkpoint.feeAssetPriceModifier,
|
|
74
|
+
signatureContext,
|
|
75
|
+
);
|
|
76
|
+
const checkpointInfo: CheckpointInfo = {
|
|
77
|
+
archive: checkpoint.archiveRoot,
|
|
78
|
+
lastArchive: checkpoint.header.lastArchiveRoot,
|
|
79
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
80
|
+
checkpointNumber: checkpoint.checkpointNumber,
|
|
81
|
+
timestamp: checkpoint.header.timestamp,
|
|
82
|
+
};
|
|
83
|
+
return validateAttestations(payload, checkpoint.attestations, checkpointInfo, epochCache, constants, logger);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Core attestation validation over a consensus payload, its attestations, and checkpoint metadata --
|
|
88
|
+
* independent of whether the checkpoint's blocks have been decoded from blobs. Returns true if the
|
|
89
|
+
* attestations are valid and sufficient, false otherwise.
|
|
90
|
+
*/
|
|
91
|
+
async function validateAttestations(
|
|
92
|
+
payload: ConsensusPayload,
|
|
93
|
+
attestations: CommitteeAttestation[],
|
|
94
|
+
checkpointInfo: CheckpointInfo,
|
|
95
|
+
epochCache: EpochCache,
|
|
96
|
+
constants: Pick<L1RollupConstants, 'epochDuration'>,
|
|
97
|
+
logger?: Logger,
|
|
98
|
+
): Promise<ValidateCheckpointResult> {
|
|
99
|
+
const attestorInfos = getAttestationInfoFromPayload(payload, attestations);
|
|
100
|
+
const attestors = compactArray(attestorInfos.map(info => ('address' in info ? info.address : undefined)));
|
|
101
|
+
const headerHash = payload.header.hash();
|
|
102
|
+
const archiveRoot = payload.archive.toString();
|
|
103
|
+
const slot = payload.header.slotNumber;
|
|
104
|
+
const checkpointNumber = checkpointInfo.checkpointNumber;
|
|
46
105
|
const epoch: EpochNumber = getEpochAtSlot(slot, constants);
|
|
47
106
|
const { committee, seed } = await epochCache.getCommitteeForEpoch(epoch);
|
|
48
|
-
const logData = { checkpointNumber
|
|
107
|
+
const logData = { checkpointNumber, slot, epoch, headerHash, archiveRoot };
|
|
49
108
|
|
|
50
|
-
logger?.debug(`Validating attestations for checkpoint ${
|
|
109
|
+
logger?.debug(`Validating attestations for checkpoint ${checkpointNumber} at slot ${slot} in epoch ${epoch}`, {
|
|
51
110
|
committee: (committee ?? []).map(member => member.toString()),
|
|
52
111
|
recoveredAttestors: attestorInfos,
|
|
53
112
|
postedAttestations: attestations.map(a => (a.address.isZero() ? a.signature : a.address).toString()),
|
|
@@ -72,7 +131,7 @@ export async function validateCheckpointAttestations(
|
|
|
72
131
|
const failedValidationResult = <TReason extends ValidateCheckpointNegativeResult['reason']>(reason: TReason) => ({
|
|
73
132
|
valid: false as const,
|
|
74
133
|
reason,
|
|
75
|
-
checkpoint:
|
|
134
|
+
checkpoint: checkpointInfo,
|
|
76
135
|
committee,
|
|
77
136
|
seed,
|
|
78
137
|
epoch,
|
|
@@ -123,7 +182,7 @@ export async function validateCheckpointAttestations(
|
|
|
123
182
|
}
|
|
124
183
|
|
|
125
184
|
logger?.debug(
|
|
126
|
-
`Checkpoint attestations validated successfully for checkpoint ${
|
|
185
|
+
`Checkpoint attestations validated successfully for checkpoint ${checkpointNumber} at slot ${slot}`,
|
|
127
186
|
logData,
|
|
128
187
|
);
|
|
129
188
|
return { valid: true };
|
package/src/store/block_store.ts
CHANGED
|
@@ -1167,14 +1167,13 @@ export class BlockStore {
|
|
|
1167
1167
|
}
|
|
1168
1168
|
|
|
1169
1169
|
/**
|
|
1170
|
-
* Resolves all
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
*
|
|
1176
|
-
*
|
|
1177
|
-
* back to checkpointed when no pending checkpoint exists).
|
|
1170
|
+
* Resolves all four L2 chain tips (proposed, checkpointed, proven, finalized) in a single
|
|
1171
|
+
* read-only transaction so the snapshot is internally consistent. Each underlying record is
|
|
1172
|
+
* read at most once: latest block and latest confirmed checkpoint are loaded directly (no
|
|
1173
|
+
* separate "find the number, then look up data" hop), the proven/finalized checkpoint
|
|
1174
|
+
* singletons are read once and their storage entries are reused if they coincide with the
|
|
1175
|
+
* latest checkpoint, and per-tip block hashes are deduped when two tips land on the same block
|
|
1176
|
+
* (e.g. finalized == proven).
|
|
1178
1177
|
*
|
|
1179
1178
|
* The result is guaranteed to satisfy `finalized <= proven <= checkpointed <= proposed` (by
|
|
1180
1179
|
* block number). Genesis is represented by `(INITIAL_L2_BLOCK_NUM - 1)` and the supplied
|
|
@@ -1197,9 +1196,6 @@ export class BlockStore {
|
|
|
1197
1196
|
|
|
1198
1197
|
// Load latest block and checkpoint entries
|
|
1199
1198
|
const [latestBlockEntry] = await toArray(this.#blocks.entriesAsync({ reverse: true, limit: 1 }));
|
|
1200
|
-
const [proposedCheckpointEntry] = await toArray(
|
|
1201
|
-
this.#proposedCheckpoints.entriesAsync({ reverse: true, limit: 1 }),
|
|
1202
|
-
);
|
|
1203
1199
|
const [latestCheckpointEntry] = await toArray(this.#checkpoints.entriesAsync({ reverse: true, limit: 1 }));
|
|
1204
1200
|
const latestCheckpointNumber = latestCheckpointEntry
|
|
1205
1201
|
? CheckpointNumber(latestCheckpointEntry[0])
|
|
@@ -1285,14 +1281,6 @@ export class BlockStore {
|
|
|
1285
1281
|
const provenTip = await buildTipFromCheckpoint(provenCheckpoint);
|
|
1286
1282
|
const finalizedTip = await buildTipFromCheckpoint(finalizedCheckpoint);
|
|
1287
1283
|
|
|
1288
|
-
// Proposed checkpoint falls back to the checkpoint tip if it's not set. And if local storage is
|
|
1289
|
-
// inconsistent and the proposed checkpoint is behind the checkpointed tip, we patch that and
|
|
1290
|
-
// report the checkpointed tip as the proposed checkpoint to maintain the invariant.
|
|
1291
|
-
const proposedCheckpointTip =
|
|
1292
|
-
proposedCheckpointEntry === undefined || proposedCheckpointEntry[0] <= latestCheckpointNumber
|
|
1293
|
-
? checkpointedTip
|
|
1294
|
-
: await buildTipFromCheckpoint(proposedCheckpointEntry[1]);
|
|
1295
|
-
|
|
1296
1284
|
// A checkpointed block past the latest stored block would mean a checkpoint
|
|
1297
1285
|
// references blocks that aren't in blocks.
|
|
1298
1286
|
if (proposedBlockId.number < checkpointedTip.block.number) {
|
|
@@ -1304,11 +1292,10 @@ export class BlockStore {
|
|
|
1304
1292
|
// Assert that checkpoint numbers are increasing
|
|
1305
1293
|
if (
|
|
1306
1294
|
finalizedTip.checkpoint.number > provenTip.checkpoint.number ||
|
|
1307
|
-
provenTip.checkpoint.number > checkpointedTip.checkpoint.number
|
|
1308
|
-
checkpointedTip.checkpoint.number > proposedCheckpointTip.checkpoint.number
|
|
1295
|
+
provenTip.checkpoint.number > checkpointedTip.checkpoint.number
|
|
1309
1296
|
) {
|
|
1310
1297
|
throw new Error(
|
|
1311
|
-
`Inconsistent checkpoint numbers in chain tips: finalized=${finalizedTip.checkpoint.number} proven=${provenTip.checkpoint.number} checkpointed=${checkpointedTip.checkpoint.number}
|
|
1298
|
+
`Inconsistent checkpoint numbers in chain tips: finalized=${finalizedTip.checkpoint.number} proven=${provenTip.checkpoint.number} checkpointed=${checkpointedTip.checkpoint.number}`,
|
|
1312
1299
|
);
|
|
1313
1300
|
}
|
|
1314
1301
|
|
|
@@ -1316,17 +1303,15 @@ export class BlockStore {
|
|
|
1316
1303
|
if (
|
|
1317
1304
|
finalizedTip.block.number > provenTip.block.number ||
|
|
1318
1305
|
provenTip.block.number > checkpointedTip.block.number ||
|
|
1319
|
-
checkpointedTip.block.number >
|
|
1320
|
-
proposedCheckpointTip.block.number > proposedBlockId.number
|
|
1306
|
+
checkpointedTip.block.number > proposedBlockId.number
|
|
1321
1307
|
) {
|
|
1322
1308
|
throw new Error(
|
|
1323
|
-
`Inconsistent block numbers in chain tips: finalized=${finalizedTip.block.number} proven=${provenTip.block.number} checkpointed=${checkpointedTip.block.number}
|
|
1309
|
+
`Inconsistent block numbers in chain tips: finalized=${finalizedTip.block.number} proven=${provenTip.block.number} checkpointed=${checkpointedTip.block.number} proposed=${proposedBlockId.number}`,
|
|
1324
1310
|
);
|
|
1325
1311
|
}
|
|
1326
1312
|
|
|
1327
1313
|
return {
|
|
1328
1314
|
proposed: proposedBlockId,
|
|
1329
|
-
proposedCheckpoint: proposedCheckpointTip,
|
|
1330
1315
|
checkpointed: checkpointedTip,
|
|
1331
1316
|
proven: provenTip,
|
|
1332
1317
|
finalized: finalizedTip,
|
|
@@ -2,6 +2,7 @@ import { Fr } from '@aztec/foundation/curves/bn254';
|
|
|
2
2
|
import { toArray } from '@aztec/foundation/iterable';
|
|
3
3
|
import { BufferReader, numToUInt8, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
4
4
|
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
5
|
+
import { isProtocolContractClass } from '@aztec/protocol-contracts';
|
|
5
6
|
import type {
|
|
6
7
|
ContractClassPublic,
|
|
7
8
|
ContractClassPublicWithBlockNumber,
|
|
@@ -50,6 +51,12 @@ export class ContractClassStore {
|
|
|
50
51
|
await this.db.transactionAsync(async () => {
|
|
51
52
|
const key = contractClass.id.toString();
|
|
52
53
|
if (await this.#contractClasses.hasAsync(key)) {
|
|
54
|
+
// Protocol contracts are preloaded at block 0, so a later on-chain (re-)publish of a bundled
|
|
55
|
+
// protocol class id is valid and must be a no-op. Keep the existing block-0 entry untouched
|
|
56
|
+
// (do not bump its block number) so it survives reorgs of the publishing block.
|
|
57
|
+
if (isProtocolContractClass(contractClass.id)) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
53
60
|
throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
|
|
54
61
|
}
|
|
55
62
|
await this.#contractClasses.set(
|
|
@@ -61,6 +68,11 @@ export class ContractClassStore {
|
|
|
61
68
|
}
|
|
62
69
|
|
|
63
70
|
async deleteContractClass(contractClass: ContractClassPublic, blockNumber: number): Promise<void> {
|
|
71
|
+
// Protocol contracts are preloaded at block 0 and must never be deleted, even when the block that
|
|
72
|
+
// (re-)published them on-chain is unwound by a reorg.
|
|
73
|
+
if (isProtocolContractClass(contractClass.id)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
64
76
|
const restoredContractClass = await this.#contractClasses.getAsync(contractClass.id.toString());
|
|
65
77
|
if (restoredContractClass && deserializeContractClassPublic(restoredContractClass).l2BlockNumber >= blockNumber) {
|
|
66
78
|
await this.db.transactionAsync(async () => {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
2
|
import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
|
|
3
|
+
import { isProtocolContract } from '@aztec/protocol-contracts';
|
|
3
4
|
import type { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
4
5
|
import {
|
|
5
6
|
type ContractInstanceUpdateWithAddress,
|
|
@@ -72,6 +73,11 @@ export class ContractInstanceStore {
|
|
|
72
73
|
return this.db.transactionAsync(async () => {
|
|
73
74
|
const key = contractInstance.address.toString();
|
|
74
75
|
if (await this.#contractInstances.hasAsync(key)) {
|
|
76
|
+
// Protocol contracts are preloaded at block 0, so a later on-chain (re-)publish of a bundled
|
|
77
|
+
// protocol instance is valid and must be a no-op. Keep the existing block-0 entry untouched.
|
|
78
|
+
if (isProtocolContract(contractInstance.address)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
75
81
|
throw new Error(
|
|
76
82
|
`Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
|
|
77
83
|
);
|
|
@@ -82,6 +88,11 @@ export class ContractInstanceStore {
|
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
deleteContractInstance(contractInstance: ContractInstanceWithAddress): Promise<void> {
|
|
91
|
+
// Protocol contracts are preloaded at block 0 and must never be deleted, even when the block that
|
|
92
|
+
// (re-)published them on-chain is unwound by a reorg.
|
|
93
|
+
if (isProtocolContract(contractInstance.address)) {
|
|
94
|
+
return Promise.resolve();
|
|
95
|
+
}
|
|
85
96
|
return this.db.transactionAsync(async () => {
|
|
86
97
|
await this.#contractInstances.delete(contractInstance.address.toString());
|
|
87
98
|
await this.#contractInstancePublishedAt.delete(contractInstance.address.toString());
|
package/src/store/log_store.ts
CHANGED
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
endOfTxRange,
|
|
27
27
|
fieldHex,
|
|
28
28
|
incKey,
|
|
29
|
+
tagHexForLog,
|
|
29
30
|
} from './log_store_codec.js';
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -100,7 +101,7 @@ export class LogStore {
|
|
|
100
101
|
let publicLogIndexWithinTx = 0;
|
|
101
102
|
|
|
102
103
|
for (const log of txEffect.privateLogs) {
|
|
103
|
-
const tagHex =
|
|
104
|
+
const tagHex = tagHexForLog(log.fields);
|
|
104
105
|
const key = encodeKey(tagHex, blockNumber, txIndexWithinBlock, privateLogIndexWithinTx);
|
|
105
106
|
const value = encodeValue({
|
|
106
107
|
txHash,
|
|
@@ -115,7 +116,7 @@ export class LogStore {
|
|
|
115
116
|
|
|
116
117
|
for (const log of txEffect.publicLogs) {
|
|
117
118
|
const contractHex = fieldHex(log.contractAddress);
|
|
118
|
-
const tagHex =
|
|
119
|
+
const tagHex = tagHexForLog(log.fields);
|
|
119
120
|
const key = encodeKey(
|
|
120
121
|
encodePublicPrefix(contractHex, tagHex),
|
|
121
122
|
blockNumber,
|
|
@@ -37,6 +37,17 @@ export function fieldHex(value: Fr | { toString: () => string }): string {
|
|
|
37
37
|
return value.toString().slice(2);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Tag prefix for a log: the hex of its first field, or the empty string when the log carries no fields.
|
|
42
|
+
* A protocol-valid public log can have zero fields (e.g. a raw AVM `EMITPUBLICLOG` with `logSize = 0`),
|
|
43
|
+
* which has no tag to index by. Encoding it under the empty tag keeps it retrievable via the per-block
|
|
44
|
+
* read (and droppable on reorg) while never matching a real 64-hex-char tag query — instead of reading
|
|
45
|
+
* `fields[0]` off an empty array and aborting the whole block-ingestion transaction.
|
|
46
|
+
*/
|
|
47
|
+
export function tagHexForLog(fields: Fr[]): string {
|
|
48
|
+
return fields.length > 0 ? fieldHex(fields[0]) : '';
|
|
49
|
+
}
|
|
50
|
+
|
|
40
51
|
/** Encodes a number as 8-char zero-padded lowercase hex (matches a u32 big-endian byte buffer's lex order). */
|
|
41
52
|
export function u32Hex(n: number): string {
|
|
42
53
|
return n.toString(16).padStart(NUMERIC_HEX_LEN, '0');
|
|
@@ -55,7 +55,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
55
55
|
private provenBlockNumber: number = 0;
|
|
56
56
|
private finalizedBlockNumber: number = 0;
|
|
57
57
|
private checkpointedBlockNumber: number = 0;
|
|
58
|
-
private proposedCheckpointBlockNumber: number = 0;
|
|
59
58
|
|
|
60
59
|
private initialHeader: BlockHeader = BlockHeader.empty();
|
|
61
60
|
private initialHeaderHash: BlockHash = GENESIS_BLOCK_HEADER_HASH;
|
|
@@ -164,7 +163,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
164
163
|
});
|
|
165
164
|
// Keep tip numbers consistent with remaining blocks.
|
|
166
165
|
this.checkpointedBlockNumber = Math.min(this.checkpointedBlockNumber, maxBlockNum);
|
|
167
|
-
this.proposedCheckpointBlockNumber = Math.min(this.proposedCheckpointBlockNumber, maxBlockNum);
|
|
168
166
|
this.provenBlockNumber = Math.min(this.provenBlockNumber, maxBlockNum);
|
|
169
167
|
this.finalizedBlockNumber = Math.min(this.finalizedBlockNumber, maxBlockNum);
|
|
170
168
|
this.log.verbose(`Removed ${numBlocks} blocks from the mock L2 block source`);
|
|
@@ -181,17 +179,9 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
181
179
|
this.finalizedBlockNumber = finalizedBlockNumber;
|
|
182
180
|
}
|
|
183
181
|
|
|
184
|
-
public setProposedCheckpointBlockNumber(blockNumber: number) {
|
|
185
|
-
this.proposedCheckpointBlockNumber = blockNumber;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
182
|
public setCheckpointedBlockNumber(checkpointedBlockNumber: number) {
|
|
189
183
|
const prevCheckpointed = this.checkpointedBlockNumber;
|
|
190
184
|
this.checkpointedBlockNumber = checkpointedBlockNumber;
|
|
191
|
-
// Proposed checkpoint is always at least as advanced as checkpointed
|
|
192
|
-
if (this.proposedCheckpointBlockNumber < checkpointedBlockNumber) {
|
|
193
|
-
this.proposedCheckpointBlockNumber = checkpointedBlockNumber;
|
|
194
|
-
}
|
|
195
185
|
// Auto-create single-block checkpoints for newly checkpointed blocks that don't have one yet.
|
|
196
186
|
// This handles blocks added via addProposedBlocks that are now being marked as checkpointed.
|
|
197
187
|
const newCheckpoints: Checkpoint[] = [];
|
|
@@ -255,10 +245,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
255
245
|
return block ? block.header.globalVariables.blockNumber : undefined;
|
|
256
246
|
}
|
|
257
247
|
|
|
258
|
-
public getProposedCheckpointL2BlockNumber() {
|
|
259
|
-
return Promise.resolve(BlockNumber(this.proposedCheckpointBlockNumber));
|
|
260
|
-
}
|
|
261
|
-
|
|
262
248
|
public getCheckpoint(query: CheckpointQuery): Promise<PublishedCheckpoint | undefined> {
|
|
263
249
|
const checkpoint = this.resolveCheckpointQuery(query);
|
|
264
250
|
if (!checkpoint) {
|
|
@@ -373,19 +359,17 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
373
359
|
}
|
|
374
360
|
|
|
375
361
|
async getL2Tips(): Promise<L2Tips> {
|
|
376
|
-
const [latest, proven, finalized, checkpointed
|
|
362
|
+
const [latest, proven, finalized, checkpointed] = [
|
|
377
363
|
await this.getBlockNumber(),
|
|
378
364
|
this.provenBlockNumber,
|
|
379
365
|
this.finalizedBlockNumber,
|
|
380
366
|
this.checkpointedBlockNumber,
|
|
381
|
-
await this.getProposedCheckpointL2BlockNumber(),
|
|
382
367
|
] as const;
|
|
383
368
|
|
|
384
369
|
const latestBlock = this.l2Blocks[latest - 1];
|
|
385
370
|
const provenBlock = this.l2Blocks[proven - 1];
|
|
386
371
|
const finalizedBlock = this.l2Blocks[finalized - 1];
|
|
387
372
|
const checkpointedBlock = this.l2Blocks[checkpointed - 1];
|
|
388
|
-
const proposedCheckpointBlock = this.l2Blocks[proposedCheckpoint - 1];
|
|
389
373
|
|
|
390
374
|
// For genesis tips (block number 0) report the dynamic initial header hash so consumers
|
|
391
375
|
// running L2BlockStream against this mock agree at block 0 with their local tip store.
|
|
@@ -413,10 +397,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
413
397
|
number: BlockNumber(checkpointed),
|
|
414
398
|
hash: await tipHash(checkpointedBlock, checkpointed),
|
|
415
399
|
};
|
|
416
|
-
const proposedCheckpointBlockId = {
|
|
417
|
-
number: BlockNumber(proposedCheckpoint),
|
|
418
|
-
hash: await tipHash(proposedCheckpointBlock, proposedCheckpoint),
|
|
419
|
-
};
|
|
420
400
|
|
|
421
401
|
const makeTipId = (blockId: typeof latestBlockId) => {
|
|
422
402
|
const checkpointNumber = this.findCheckpointNumberForBlock(blockId.number) ?? CheckpointNumber(0);
|
|
@@ -435,7 +415,6 @@ export class MockL2BlockSource implements L2BlockSource, ContractDataSource {
|
|
|
435
415
|
checkpointed: makeTipId(checkpointedBlockId),
|
|
436
416
|
proven: makeTipId(provenBlockId),
|
|
437
417
|
finalized: makeTipId(finalizedBlockId),
|
|
438
|
-
proposedCheckpoint: makeTipId(proposedCheckpointBlockId),
|
|
439
418
|
};
|
|
440
419
|
}
|
|
441
420
|
|
package/src/test/mock_structs.ts
CHANGED
|
@@ -269,7 +269,10 @@ export function makePublicLogTag(blockNumber: number, txIndex: number, logIndex:
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
/** Creates a PublicLog with fields derived from the tag. */
|
|
272
|
-
export function makePublicLog(
|
|
272
|
+
export function makePublicLog(
|
|
273
|
+
tag: Tag,
|
|
274
|
+
contractAddress: AztecAddress = AztecAddress.fromNumberUnsafe(543254),
|
|
275
|
+
): PublicLog {
|
|
273
276
|
return PublicLog.from({
|
|
274
277
|
contractAddress,
|
|
275
278
|
fields: new Array(10).fill(null).map((_, i) => (!i ? tag.value : new Fr(tag.value.toBigInt() + BigInt(i)))),
|
|
@@ -281,7 +284,7 @@ export function makePublicLogs(
|
|
|
281
284
|
blockNumber: number,
|
|
282
285
|
txIndex: number,
|
|
283
286
|
numLogsPerTx: number,
|
|
284
|
-
contractAddress: AztecAddress = AztecAddress.
|
|
287
|
+
contractAddress: AztecAddress = AztecAddress.fromNumberUnsafe(543254),
|
|
285
288
|
): PublicLog[] {
|
|
286
289
|
return times(numLogsPerTx, logIndex => {
|
|
287
290
|
const tag = makePublicLogTag(blockNumber, txIndex, logIndex);
|