Skip to content

Commit

Permalink
fix: L1 deployment on reth (#12060)
Browse files Browse the repository at this point in the history
Fixes deployment of L1 contracts with salt on reth. The deployment was
failing because `reth` fails to estimate gas when deploying a contract
that relies on external libraries deployed in an unmined tx. This PR
makes the deployment of the contract to wait for dependent libraries to
be deployed.

Unfortunately I didn't manage to get eth-devnet into unit tests, but
added env vars to configure the `deploy-l1-contracts` test to try it
locally.

Fixes #11576
  • Loading branch information
spalladino authored Feb 18, 2025
1 parent c067772 commit f61fe9d
Show file tree
Hide file tree
Showing 15 changed files with 63 additions and 39 deletions.
1 change: 1 addition & 0 deletions spartan/aztec-network/eth-devnet/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ services:
- "${PWD}/out/jwt-secret.hex:/genesis/jwt-secret.hex"
environment:
- HTTP_PORT=8545
- WS_PORT=8546
- MAX_TX_INPUT_SIZE_BYTES=1310720

eth_beacon:
Expand Down
2 changes: 1 addition & 1 deletion spartan/aztec-network/eth-devnet/run-locally.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
REPO_ROOT=$(git rev-parse --show-toplevel)

${REPO_ROOT}/spartan/aztec-network/eth-devnet/create.sh
(cd ${REPO_ROOT}/spartan/aztec-network/eth-devnet && docker compose build && docker compose up)
(cd ${REPO_ROOT}/spartan/aztec-network/eth-devnet && docker compose down -v && docker compose build && docker compose up)
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/fixtures/snapshot_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ async function setupFromFresh(

// Start anvil. We go via a wrapper script to ensure if the parent dies, anvil dies.
logger.verbose('Starting anvil...');
const res = await startAnvil(opts.ethereumSlotDuration);
const res = await startAnvil({ l1BlockTime: opts.ethereumSlotDuration });
const anvil = res.anvil;
aztecNodeConfig.l1RpcUrl = res.rpcUrl;

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ export async function setup(
);
}

const res = await startAnvil(opts.ethereumSlotDuration);
const res = await startAnvil({ l1BlockTime: opts.ethereumSlotDuration });
anvil = res.anvil;
config.l1RpcUrl = res.rpcUrl;
}
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/ethereum/src/contracts/forwarder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
import { foundry } from 'viem/chains';

import { DefaultL1ContractsConfig } from '../config.js';
import { type L1Clients, createL1Clients, deployL1Contract, deployL1Contracts } from '../deploy_l1_contracts.js';
import { createL1Clients, deployL1Contract, deployL1Contracts } from '../deploy_l1_contracts.js';
import { L1TxUtils } from '../l1_tx_utils.js';
import { startAnvil } from '../test/start_anvil.js';
import { type L1Clients } from '../types.js';
import { FormattedViemError } from '../utils.js';
import { ForwarderContract } from './forwarder.js';

Expand Down
3 changes: 2 additions & 1 deletion yarn-project/ethereum/src/contracts/forwarder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
getContract,
} from 'viem';

import { type L1Clients, deployL1Contract } from '../deploy_l1_contracts.js';
import { deployL1Contract } from '../deploy_l1_contracts.js';
import { type L1BlobInputs, type L1GasConfig, type L1TxRequest, type L1TxUtils } from '../l1_tx_utils.js';
import { type L1Clients } from '../types.js';
import { RollupContract } from './rollup.js';

export class ForwarderContract {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/contracts/governance_proposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
getContract,
} from 'viem';

import type { L1Clients } from '../deploy_l1_contracts.js';
import type { GasPrice, L1TxRequest, L1TxUtils } from '../l1_tx_utils.js';
import { type L1Clients } from '../types.js';
import { type IEmpireBase, encodeVote } from './empire_base.js';

export class GovernanceProposerContract implements IEmpireBase {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/contracts/slashing_proposer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
getContract,
} from 'viem';

import type { L1Clients } from '../deploy_l1_contracts.js';
import type { L1TxRequest } from '../l1_tx_utils.js';
import type { L1Clients } from '../types.js';
import { type IEmpireBase, encodeVote } from './empire_base.js';

export class SlashingProposerContract implements IEmpireBase {
Expand Down
27 changes: 20 additions & 7 deletions yarn-project/ethereum/src/deploy_l1_contracts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ import { Fr } from '@aztec/foundation/fields';
import { type Logger, createLogger } from '@aztec/foundation/log';
import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';

import { type Anvil } from '@viem/anvil';
import { getContract } from 'viem';
import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
import { foundry } from 'viem/chains';

import { createEthereumChain } from './chain.js';
import { DefaultL1ContractsConfig } from './config.js';
import { type DeployL1ContractsArgs, deployL1Contracts } from './deploy_l1_contracts.js';
import { startAnvil } from './test/start_anvil.js';

describe('deploy_l1_contracts', () => {
let anvil: Anvil;
let rpcUrl: string;
let privateKey: PrivateKeyAccount;
let logger: Logger;

Expand All @@ -27,6 +24,13 @@ describe('deploy_l1_contracts', () => {
let initialValidators: EthAddress[];
let l2FeeJuiceAddress: AztecAddress;

// Use these environment variables to run against a live node. Eg to test against spartan's eth-devnet:
// BLOCK_TIME=1 spartan/aztec-network/eth-devnet/run-locally.sh
// LOG_LEVEL=verbose L1_RPC_URL=http://localhost:8545 L1_CHAIN_ID=1337 yarn test deploy_l1_contracts
const chainId = process.env.L1_CHAIN_ID ? parseInt(process.env.L1_CHAIN_ID, 10) : 31337;
let rpcUrl = process.env.L1_RPC_URL;
let stop: () => Promise<void> = () => Promise.resolve();

beforeAll(async () => {
logger = createLogger('ethereum:test:deploy_l1_contracts');
privateKey = privateKeyToAccount('0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba');
Expand All @@ -37,22 +41,31 @@ describe('deploy_l1_contracts', () => {
initialValidators = times(3, EthAddress.random);
l2FeeJuiceAddress = await AztecAddress.random();

({ anvil, rpcUrl } = await startAnvil());
if (!rpcUrl) {
({ stop, rpcUrl } = await startAnvil());
}
});

afterAll(async () => {
await anvil.stop().catch(err => createLogger('cleanup').error(err));
if (stop) {
try {
await stop();
} catch (err) {
createLogger('ethereum:cleanup').error(`Error during cleanup`, err);
}
}
});

const deploy = (args: Partial<DeployL1ContractsArgs> = {}) =>
deployL1Contracts(rpcUrl, privateKey, foundry, logger, {
deployL1Contracts(rpcUrl!, privateKey, createEthereumChain(rpcUrl!, chainId).chainInfo, logger, {
...DefaultL1ContractsConfig,
salt: undefined,
vkTreeRoot,
protocolContractTreeRoot,
genesisArchiveRoot,
genesisBlockHash,
l2FeeJuiceAddress,
l1TxConfig: { checkIntervalMs: 100 },
...args,
});

Expand Down
36 changes: 17 additions & 19 deletions yarn-project/ethereum/src/deploy_l1_contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,10 @@ import type { Abi, Narrow } from 'abitype';
import {
type Account,
type Chain,
type Client,
type Hex,
type HttpTransport,
type PublicActions,
type PublicClient,
type PublicRpcSchema,
type WalletActions,
type WalletClient,
type WalletRpcSchema,
concatHex,
createPublicClient,
createWalletClient,
Expand All @@ -68,6 +63,7 @@ import { isAnvilTestChain } from './chain.js';
import { type L1ContractsConfig } from './config.js';
import { type L1ContractAddresses } from './l1_contract_addresses.js';
import { L1TxUtils, type L1TxUtilsConfig, defaultL1TxUtilsConfig } from './l1_tx_utils.js';
import type { L1Clients } from './types.js';

export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';

Expand Down Expand Up @@ -206,19 +202,10 @@ export interface DeployL1ContractsArgs extends L1ContractsConfig {
salt: number | undefined;
/** The initial validators for the rollup contract. */
initialValidators?: EthAddress[];
/** Configuration for the L1 tx utils module. */
l1TxConfig?: Partial<L1TxUtilsConfig>;
}

export type L1Clients = {
publicClient: PublicClient<HttpTransport, Chain>;
walletClient: Client<
HttpTransport,
Chain,
PrivateKeyAccount,
[...WalletRpcSchema, ...PublicRpcSchema],
PublicActions<HttpTransport, Chain> & WalletActions<Chain, PrivateKeyAccount>
>;
};

/**
* Creates a wallet and a public viem client for interacting with L1.
* @param rpcUrl - RPC URL to connect to L1.
Expand Down Expand Up @@ -353,7 +340,7 @@ export const deployL1Contracts = async (
await govDeployer.waitForDeployments();
logger.verbose(`All governance contracts deployed`);

const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger);
const deployer = new L1Deployer(walletClient, publicClient, args.salt, logger, args.l1TxConfig ?? {});

const feeJuicePortalAddress = await deployer.deploy(l1Artifacts.feeJuicePortal, [
registryAddress.toString(),
Expand Down Expand Up @@ -665,11 +652,11 @@ export async function deployL1Contract(
}

const replacements: Record<string, EthAddress> = {};

const libraryTxs: Hex[] = [];
for (const libraryName in libraries?.libraryCode) {
const lib = libraries.libraryCode[libraryName];

const { address } = await deployL1Contract(
const { address, txHash } = await deployL1Contract(
walletClient,
publicClient,
lib.contractAbi,
Expand All @@ -681,6 +668,10 @@ export async function deployL1Contract(
l1TxUtils,
);

if (txHash) {
libraryTxs.push(txHash);
}

for (const linkRef in libraries.linkReferences) {
for (const contractName in libraries.linkReferences[linkRef]) {
// If the library name matches the one we just deployed, we replace it.
Expand All @@ -706,6 +697,13 @@ export async function deployL1Contract(
const replacement = replacements[toReplace].toString().slice(2);
bytecode = bytecode.replace(new RegExp(escapeRegExp(toReplace), 'g'), replacement) as Hex;
}

// Reth fails gas estimation if the deployed contract attempts to call a library that is not yet deployed,
// so we wait for all library deployments to be mined before deploying the contract.
if (libraryTxs.length > 0) {
logger?.verbose(`Awaiting for linked libraries to be deployed`);
await Promise.all(libraryTxs.map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })));
}
}

if (maybeSalt) {
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/l1_tx_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ describe('GasUtils', () => {
const logger = createLogger('ethereum:test:l1_gas_test');

beforeAll(async () => {
const { anvil: anvilInstance, rpcUrl } = await startAnvil(1);
const { anvil: anvilInstance, rpcUrl } = await startAnvil({ l1BlockTime: 1 });
anvil = anvilInstance;
cheatCodes = new EthCheatCodes(rpcUrl);
const hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: 0 });
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/l1_tx_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
hexToBytes,
} from 'viem';

import { type L1Clients } from './deploy_l1_contracts.js';
import { type L1Clients } from './types.js';
import { formatViemError } from './utils.js';

// 1_000_000_000 Gwei = 1 ETH
Expand Down
10 changes: 7 additions & 3 deletions yarn-project/ethereum/src/test/start_anvil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import { dirname, resolve } from 'path';
/**
* Ensures there's a running Anvil instance and returns the RPC URL.
*/
export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil; rpcUrl: string }> {
export async function startAnvil(
opts: {
l1BlockTime?: number;
} = {},
): Promise<{ anvil: Anvil; rpcUrl: string; stop: () => Promise<void> }> {
const anvilBinary = resolve(dirname(fileURLToPath(import.meta.url)), '../../', 'scripts/anvil_kill_wrapper.sh');

let port: number | undefined;
Expand All @@ -19,7 +23,7 @@ export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil;
const anvil = createAnvil({
anvilBinary,
port: 0,
blockTime: l1BlockTime,
blockTime: opts.l1BlockTime,
stopTimeout: 1000,
});

Expand All @@ -44,5 +48,5 @@ export async function startAnvil(l1BlockTime?: number): Promise<{ anvil: Anvil;

// Monkeypatch the anvil instance to include the actually assigned port
Object.defineProperty(anvil, 'port', { value: port, writable: false });
return { anvil, rpcUrl: `http://127.0.0.1:${port}` };
return { anvil, stop: () => anvil.stop(), rpcUrl: `http://127.0.0.1:${port}` };
}
2 changes: 1 addition & 1 deletion yarn-project/ethereum/src/test/tx_delayer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('tx_delayer', () => {
const ETHEREUM_SLOT_DURATION = 2;

beforeAll(async () => {
({ anvil, rpcUrl } = await startAnvil(ETHEREUM_SLOT_DURATION));
({ anvil, rpcUrl } = await startAnvil({ l1BlockTime: ETHEREUM_SLOT_DURATION }));
logger = createLogger('ethereum:test:tx_delayer');
});

Expand Down
6 changes: 6 additions & 0 deletions yarn-project/ethereum/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,9 @@ export type ViemClient = Client<

/** Type for a viem public client */
export type ViemPublicClient = PublicClient<HttpTransport, Chain>;

/** Both L1 clients */
export type L1Clients = {
publicClient: ViemPublicClient;
walletClient: ViemClient;
};

1 comment on commit f61fe9d

@AztecBot
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'C++ Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.05.

Benchmark suite Current: f61fe9d Previous: c067772 Ratio
wasmconstruct_proof_ultrahonk_power_of_2/20 11630.896286000001 ms/iter 10676.128055 ms/iter 1.09

This comment was automatically generated by workflow using github-action-benchmark.

CC: @ludamad @codygunton

Please sign in to comment.