Skip to content

Commit 194567f

Browse files
authored
Merge branch 'v2' into backport-to-v2-staging
2 parents 3445563 + 06bf30a commit 194567f

207 files changed

Lines changed: 1129 additions & 518 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.test_patterns.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ tests:
252252
error_regex: "will propagate messages to peers at the same version"
253253
owners:
254254
- *palla
255-
- regex: "p2p/src/client/test/p2p_client.integration_status_handshake.test.ts"
255+
- regex: "src/client/test/p2p_client.integration_status_handshake.test.ts"
256256
error_regex: "Expected number of calls"
257257
owners:
258258
- *palla

spartan/scripts/setup_gcp_secrets.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,25 @@ for env_var in "${!SECRET_MAPPINGS[@]}"; do
5555
if grep -q "^${env_var}=REPLACE_WITH_GCP_SECRET" "$ENV_FILE"; then
5656
# Export the secret value
5757
secret_value=$(get_secret "$secret_name")
58+
echo "::add-mask::$secret_value"
5859
export $env_var="${secret_value}"
5960
elif grep -q "^${env_var}=REPLACE_WITH_GCP_SECRET/" "$ENV_FILE"; then
6061
# Handle cases like STORE_SNAPSHOT_URL=REPLACE_WITH_GCP_SECRET/network/
6162
suffix=$(grep "^${env_var}=REPLACE_WITH_GCP_SECRET/" "$ENV_FILE" | cut -d'/' -f2-)
6263
secret_value=$(get_secret "$secret_name")
64+
echo "::add-mask::$secret_value"
6365
export $env_var='${secret_value}/'$suffix
66+
elif grep -q "^${env_var}=.*REPLACE_WITH_GCP_SECRET" "$ENV_FILE"; then
67+
# Replace inline occurrences within the value, preserving surrounding content
68+
full_value=$(grep "^${env_var}=" "$ENV_FILE" | cut -d'=' -f2-)
69+
# Strip surrounding double quotes if present
70+
if [[ "$full_value" == \"*\" && "$full_value" == *\" ]]; then
71+
full_value="${full_value:1:-1}"
72+
fi
73+
secret_value=$(get_secret "$secret_name")
74+
echo "::add-mask::$secret_value"
75+
replaced_value="${full_value//REPLACE_WITH_GCP_SECRET/$secret_value}"
76+
export $env_var="$replaced_value"
6477
fi
6578
done
6679

spartan/terraform/deploy-aztec-infra/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ locals {
198198
"node.env.AWS_ACCESS_KEY_ID" = var.R2_ACCESS_KEY_ID
199199
"node.env.AWS_SECRET_ACCESS_KEY" = var.R2_SECRET_ACCESS_KEY
200200
}
201+
boot_node_host_path = "node.env.BOOT_NODE_HOST"
201202
bootstrap_nodes_path = "node.env.BOOTSTRAP_NODES"
202203
wait = true
203204
}

spartan/terraform/deploy-aztec-infra/variables.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,4 @@ variable "RPC_INGRESS_SSL_CERT_NAME" {
446446
type = string
447447
default = ""
448448
}
449+

spartan/terraform/deploy-rollup-contracts/variables.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ variable "AZTEC_DOCKER_IMAGE" {
1818
variable "L1_RPC_URLS" {
1919
description = "Comma-separated list of L1 RPC URLs"
2020
type = string
21+
sensitive = true
2122
}
2223

2324
variable "PRIVATE_KEY" {

yarn-project/archiver/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@
7878
"@aztec/protocol-contracts": "workspace:^",
7979
"@aztec/stdlib": "workspace:^",
8080
"@aztec/telemetry-client": "workspace:^",
81+
"@spalladino/viem": "2.38.2-eip7594.0",
8182
"lodash.groupby": "^4.6.0",
8283
"lodash.omit": "^4.5.0",
8384
"tsc-watch": "^6.0.0",
84-
"tslib": "^2.5.0",
85-
"viem": "2.23.7"
85+
"tslib": "^2.5.0"
8686
},
8787
"devDependencies": {
8888
"@jest/globals": "^30.0.0",

yarn-project/archiver/src/archiver/archiver.test.ts

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Blob } from '@aztec/blob-lib';
1+
import { Blob, EMPTY_BLOB_VERSIONED_HASH } from '@aztec/blob-lib';
22
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
33
import { BlobWithIndex } from '@aztec/blob-sink/types';
44
import { GENESIS_ARCHIVE_ROOT } from '@aztec/constants';
@@ -16,6 +16,7 @@ import { bufferToHex, withoutHexPrefix } from '@aztec/foundation/string';
1616
import { openTmpStore } from '@aztec/kv-store/lmdb-v2';
1717
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
1818
import {
19+
Body,
1920
CommitteeAttestation,
2021
CommitteeAttestationsAndSigners,
2122
L2Block,
@@ -28,9 +29,16 @@ import { makeAndSignCommitteeAttestationsAndSigners, makeBlockAttestationFromBlo
2829
import { getTelemetryClient } from '@aztec/telemetry-client';
2930

3031
import { jest } from '@jest/globals';
32+
import {
33+
type FormattedBlock,
34+
type Log,
35+
type Transaction,
36+
encodeFunctionData,
37+
multicall3Abi,
38+
toHex,
39+
} from '@spalladino/viem';
3140
import assert from 'assert';
3241
import { type MockProxy, mock } from 'jest-mock-extended';
33-
import { type FormattedBlock, type Log, type Transaction, encodeFunctionData, multicall3Abi, toHex } from 'viem';
3442

3543
import { Archiver } from './archiver.js';
3644
import type { ArchiverDataStore } from './archiver_store.js';
@@ -919,6 +927,71 @@ describe('Archiver', () => {
919927
await retryUntil(async () => (await archiver.getBlockNumber()) === 3, 'resync', 10, 0.1);
920928
});
921929

930+
it('handles empty blob hash without downloading blob', async () => {
931+
let latestBlockNum = await archiver.getBlockNumber();
932+
expect(latestBlockNum).toEqual(0);
933+
934+
// Create a block with an empty body
935+
const emptyBlock = blocks[0];
936+
emptyBlock.body = Body.empty();
937+
938+
const emptyBlobHash = bufferToHex(EMPTY_BLOB_VERSIONED_HASH);
939+
const rollupTx = await makeRollupTx(emptyBlock);
940+
941+
mockL1BlockNumbers(100n);
942+
943+
mockRollup.read.status.mockResolvedValue([0n, GENESIS_ROOT, 1n, emptyBlock.archive.root.toString(), GENESIS_ROOT]);
944+
945+
makeL2BlockProposedEvent(70n, 1n, emptyBlock.archive.root.toString(), [emptyBlobHash]);
946+
947+
// Mock getBlobSidecar to return empty array (simulating blob not downloaded)
948+
blobSinkClient.getBlobSidecar.mockResolvedValueOnce([]);
949+
950+
publicClient.getTransaction.mockResolvedValueOnce(rollupTx);
951+
952+
await archiver.start(false);
953+
954+
// Wait until block 1 is processed
955+
await waitUntilArchiverBlock(1);
956+
957+
latestBlockNum = await archiver.getBlockNumber();
958+
expect(latestBlockNum).toEqual(1);
959+
960+
// Verify the block was synced successfully
961+
const syncedBlock = await archiver.getBlock(1);
962+
expect(syncedBlock).toBeDefined();
963+
expect(syncedBlock!.body.txEffects.length).toEqual(0);
964+
}, 10_000);
965+
966+
it('throws error when blob hashes and bodies mismatch (non-empty case)', async () => {
967+
let latestBlockNum = await archiver.getBlockNumber();
968+
expect(latestBlockNum).toEqual(0);
969+
970+
const block = blocks[0];
971+
const blobHashes = await makeVersionedBlobHashes(block);
972+
const rollupTx = await makeRollupTx(block);
973+
974+
mockL1BlockNumbers(100n);
975+
976+
mockRollup.read.status.mockResolvedValue([0n, GENESIS_ROOT, 1n, block.archive.root.toString(), GENESIS_ROOT]);
977+
978+
makeL2BlockProposedEvent(70n, 1n, block.archive.root.toString(), blobHashes);
979+
980+
// Mock getBlobSidecar to return empty array (missing blobs)
981+
blobSinkClient.getBlobSidecar.mockResolvedValueOnce([]);
982+
983+
publicClient.getTransaction.mockResolvedValueOnce(rollupTx);
984+
985+
await archiver.start(false);
986+
987+
// Give it some time to attempt processing
988+
await sleep(1000);
989+
990+
// Should still be at block 0 since the blob fetch failed
991+
latestBlockNum = await archiver.getBlockNumber();
992+
expect(latestBlockNum).toEqual(0);
993+
}, 10_000);
994+
922995
// TODO(palla/reorg): Add a unit test for the archiver handleEpochPrune
923996
xit('handles an upcoming L2 prune', () => {});
924997

yarn-project/archiver/src/archiver/archiver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ import {
7272
trackSpan,
7373
} from '@aztec/telemetry-client';
7474

75+
import { type GetContractReturnType, createPublicClient, fallback, http } from '@spalladino/viem';
7576
import { EventEmitter } from 'events';
7677
import groupBy from 'lodash.groupby';
77-
import { type GetContractReturnType, createPublicClient, fallback, http } from 'viem';
7878

7979
import type { ArchiverDataStore, ArchiverL1SynchPoint } from './archiver_store.js';
8080
import type { ArchiverConfig } from './config.js';

yarn-project/archiver/src/archiver/data_retrieval.ts

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Blob, BlobDeserializationError } from '@aztec/blob-lib';
1+
import { Blob, BlobDeserializationError, EMPTY_BLOB_VERSIONED_HASH } from '@aztec/blob-lib';
22
import type { BlobSinkClientInterface } from '@aztec/blob-sink/client';
33
import type {
44
EpochProofPublicInputArgs,
@@ -14,6 +14,7 @@ import type { EthAddress } from '@aztec/foundation/eth-address';
1414
import type { ViemSignature } from '@aztec/foundation/eth-signature';
1515
import { Fr } from '@aztec/foundation/fields';
1616
import { type Logger, createLogger } from '@aztec/foundation/log';
17+
import { bufferToHex } from '@aztec/foundation/string';
1718
import { type InboxAbi, RollupAbi } from '@aztec/l1-artifacts';
1819
import { Body, CommitteeAttestation, L2Block, PublishedL2Block } from '@aztec/stdlib/block';
1920
import { Proof } from '@aztec/stdlib/proofs';
@@ -28,7 +29,7 @@ import {
2829
getAbiItem,
2930
hexToBytes,
3031
multicall3Abi,
31-
} from 'viem';
32+
} from '@spalladino/viem';
3233

3334
import { NoBlobBodiesFoundError } from './errors.js';
3435
import type { DataRetrieval } from './structs/data_retrieval.js';
@@ -340,38 +341,60 @@ async function getBlockFromRollupTx(
340341
// TODO(md): why is the proposed block header different to the actual block header?
341342
// This is likely going to be a footgun
342343
const header = ProposedBlockHeader.fromViem(decodedArgs.header);
344+
const body = await getBlockBodyFromBlobs(blobSinkClient, blockHash!, blobHashes, l2BlockNumber, logger);
345+
346+
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
347+
348+
const stateReference = StateReference.fromViem(decodedArgs.stateReference);
349+
350+
return {
351+
l2BlockNumber,
352+
archiveRoot,
353+
stateReference,
354+
header,
355+
body,
356+
attestations,
357+
};
358+
}
359+
360+
async function getBlockBodyFromBlobs(
361+
blobSinkClient: BlobSinkClientInterface,
362+
blockHash: string,
363+
blobHashes: Buffer<ArrayBufferLike>[],
364+
l2BlockNumber: number,
365+
logger: Logger,
366+
) {
343367
const blobBodies = await blobSinkClient.getBlobSidecar(blockHash, blobHashes);
344-
if (blobBodies.length === 0) {
345-
throw new NoBlobBodiesFoundError(l2BlockNumber);
368+
logger.trace(`Fetched ${blobBodies.length} blob bodies for L2 block ${l2BlockNumber}`, {
369+
blobHashes: blobHashes.map(bufferToHex),
370+
l2BlockNumber,
371+
});
372+
373+
if (blobBodies.length !== blobHashes.length) {
374+
// If there is exactly one blob hash and it is the empty blob hash, we are fine with not downloading it
375+
// and just defaulting to an empty block body.
376+
if (blobHashes.length === 1 && blobBodies.length === 0 && blobHashes[0].equals(EMPTY_BLOB_VERSIONED_HASH)) {
377+
logger.verbose(`Ignoring error fetching blob body for block ${l2BlockNumber} as it is empty`);
378+
return Body.empty();
379+
} else {
380+
throw new NoBlobBodiesFoundError(l2BlockNumber, blobBodies.length, blobHashes.length);
381+
}
346382
}
347383

348384
let blockFields: Fr[];
349385
try {
350386
blockFields = Blob.toEncodedFields(blobBodies.map(b => b.blob));
351387
} catch (err: any) {
352388
if (err instanceof BlobDeserializationError) {
353-
logger.fatal(err.message);
389+
logger.error(err.message);
354390
} else {
355-
logger.fatal('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
391+
logger.error('Unable to sync: failed to decode fetched blob, this blob was likely not created by us');
356392
}
357393
throw err;
358394
}
359395

360396
// The blob source gives us blockFields, and we must construct the body from them:
361-
const body = Body.fromBlobFields(blockFields);
362-
363-
const archiveRoot = new Fr(Buffer.from(hexToBytes(decodedArgs.archive)));
364-
365-
const stateReference = StateReference.fromViem(decodedArgs.stateReference);
366-
367-
return {
368-
l2BlockNumber,
369-
archiveRoot,
370-
stateReference,
371-
header,
372-
body,
373-
attestations,
374-
};
397+
return Body.fromBlobFields(blockFields);
375398
}
376399

377400
/** Given an L1 to L2 message, retrieves its corresponding event from the Inbox within a specific block range. */

yarn-project/archiver/src/archiver/errors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export class NoBlobBodiesFoundError extends Error {
2-
constructor(l2BlockNum: number) {
3-
super(`No blob bodies found for block ${l2BlockNum}`);
2+
constructor(l2BlockNum: number, found: number, expected: number) {
3+
super(`No blob bodies found for block ${l2BlockNum} (expected ${expected} but found ${found})`);
44
}
55
}
66

0 commit comments

Comments
 (0)