diff --git a/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md b/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md index ba35449ba137..dccc268926b4 100644 --- a/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md +++ b/docs/docs/tutorials/codealong/contract_tutorials/advanced/token_bridge/1_depositing_to_aztec.md @@ -34,7 +34,7 @@ Create a basic ERC20 contract that can mint tokens to anyone. We will use this t Create a file `TestERC20.sol` in the same folder and add: -#include_code contract /l1-contracts/test/TestERC20.sol solidity +#include_code contract /l1-contracts/src/mock/TestERC20.sol solidity Replace the openzeppelin import with this: @@ -81,7 +81,7 @@ Here we want to send a message to mint tokens privately on Aztec! Some key diffe - The content hash uses a different function name - `mint_private`. This is done to make it easy to separate concerns. If the contentHash between the public and private message was the same, then an attacker could consume a private message publicly! - Since we want to mint tokens privately, we shouldn’t specify a `to` Aztec address (remember that Ethereum is completely public). Instead, we will use a secret hash - `secretHashForRedeemingMintedNotes`. Only he who knows the preimage to the secret hash can actually mint the notes. This is similar to the mechanism we use for message consumption on L2 - Like with the public flow, we move the user’s funds to the portal -- We now send the message to the inbox with the `recipient` (the sister contract on L2 along with the version of aztec the message is intended for) and the `secretHashForL2MessageConsumption` (such that on L2, the consumption of the message can be private). +- We now send the message to the inbox with the `recipient` (the sister contract on L2 along with the version of aztec the message is intended for) and the `secretHashForL2MessageConsumption` (such that on L2, the consumption of the message can be private). Note that because L1 is public, everyone can inspect and figure out the contentHash and the recipient contract address. diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index c6f08a170e16..0ba8fe3e61d1 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -83,13 +83,14 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { constructor( IFeeJuicePortal _fpcJuicePortal, + IProofCommitmentEscrow _proofCommitmentEscrow, bytes32 _vkTreeRoot, address _ares, address[] memory _validators ) Leonidas(_ares) { epochProofVerifier = new MockVerifier(); FEE_JUICE_PORTAL = _fpcJuicePortal; - PROOF_COMMITMENT_ESCROW = new MockProofCommitmentEscrow(); + PROOF_COMMITMENT_ESCROW = _proofCommitmentEscrow; INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); OUTBOX = IOutbox(address(new Outbox(address(this)))); vkTreeRoot = _vkTreeRoot; diff --git a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol index e844400fba59..f73333c8396d 100644 --- a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol +++ b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol @@ -3,6 +3,7 @@ pragma solidity >=0.8.27; import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; interface IProofCommitmentEscrow { event Deposit(address indexed depositor, uint256 amount); @@ -17,4 +18,6 @@ interface IProofCommitmentEscrow { function stakeBond(address _prover, uint256 _amount) external; function unstakeBond(address _prover, uint256 _amount) external; function minBalanceAtTime(Timestamp _timestamp, address _prover) external view returns (uint256); + function deposits(address) external view returns (uint256); + function token() external view returns (IERC20); } diff --git a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol index 4568f3392eb2..7b03d1bce36f 100644 --- a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol +++ b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol @@ -4,10 +4,20 @@ pragma solidity >=0.8.27; import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; contract MockProofCommitmentEscrow is IProofCommitmentEscrow { + mapping(address => uint256) public deposits; + + IERC20 public immutable token; + + constructor() { + token = new TestERC20(); + } + function deposit(uint256 _amount) external override { - // do nothing + deposits[msg.sender] += _amount; } function startWithdraw(uint256 _amount) external override { diff --git a/l1-contracts/test/TestERC20.sol b/l1-contracts/src/mock/TestERC20.sol similarity index 100% rename from l1-contracts/test/TestERC20.sol rename to l1-contracts/src/mock/TestERC20.sol diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index cc481648bfa6..1debcd8a057e 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -15,11 +15,13 @@ import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Rollup} from "@aztec/core/Rollup.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {Leonidas} from "@aztec/core/Leonidas.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; import {MerkleTestUtil} from "./merkle/TestUtil.sol"; -import {TestERC20} from "./TestERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {MockProofCommitmentEscrow} from "@aztec/mock/MockProofCommitmentEscrow.sol"; import {TxsDecoderHelper} from "./decoders/helpers/TxsDecoderHelper.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; @@ -44,6 +46,7 @@ contract RollupTest is DecoderBase { TxsDecoderHelper internal txsHelper; TestERC20 internal testERC20; FeeJuicePortal internal feeJuicePortal; + IProofCommitmentEscrow internal proofCommitmentEscrow; SignatureLib.Signature[] internal signatures; @@ -70,7 +73,9 @@ contract RollupTest is DecoderBase { feeJuicePortal.initialize( address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) ); - rollup = new Rollup(feeJuicePortal, bytes32(0), address(this), new address[](0)); + proofCommitmentEscrow = new MockProofCommitmentEscrow(); + rollup = + new Rollup(feeJuicePortal, proofCommitmentEscrow, bytes32(0), address(this), new address[](0)); inbox = Inbox(address(rollup.INBOX())); outbox = Outbox(address(rollup.OUTBOX())); diff --git a/l1-contracts/test/TestERC20.t.sol b/l1-contracts/test/TestERC20.t.sol index 4f50cb73f21c..3b7abc4cfa70 100644 --- a/l1-contracts/test/TestERC20.t.sol +++ b/l1-contracts/test/TestERC20.t.sol @@ -1,7 +1,7 @@ pragma solidity ^0.8.18; import "forge-std/Test.sol"; -import {TestERC20} from "./TestERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; contract TestERC20Test is Test { TestERC20 testERC20; diff --git a/l1-contracts/test/portals/TokenPortal.t.sol b/l1-contracts/test/portals/TokenPortal.t.sol index dff8eca55b1e..d2dde1af4aa2 100644 --- a/l1-contracts/test/portals/TokenPortal.t.sol +++ b/l1-contracts/test/portals/TokenPortal.t.sol @@ -14,10 +14,11 @@ import {Hash} from "@aztec/core/libraries/crypto/Hash.sol"; import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; // Portal tokens import {TokenPortal} from "./TokenPortal.sol"; -import {TestERC20} from "../TestERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; @@ -60,7 +61,13 @@ contract TokenPortalTest is Test { function setUp() public { registry = new Registry(address(this)); testERC20 = new TestERC20(); - rollup = new Rollup(IFeeJuicePortal(address(0)), bytes32(0), address(this), new address[](0)); + rollup = new Rollup( + IFeeJuicePortal(address(0)), + IProofCommitmentEscrow(address(0)), + bytes32(0), + address(this), + new address[](0) + ); inbox = rollup.INBOX(); outbox = rollup.OUTBOX(); diff --git a/l1-contracts/test/portals/UniswapPortal.t.sol b/l1-contracts/test/portals/UniswapPortal.t.sol index afb986f90460..ce692130c8b7 100644 --- a/l1-contracts/test/portals/UniswapPortal.t.sol +++ b/l1-contracts/test/portals/UniswapPortal.t.sol @@ -15,6 +15,7 @@ import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; // Portals import {TokenPortal} from "./TokenPortal.sol"; @@ -52,7 +53,13 @@ contract UniswapPortalTest is Test { vm.selectFork(forkId); registry = new Registry(address(this)); - rollup = new Rollup(IFeeJuicePortal(address(0)), bytes32(0), address(this), new address[](0)); + rollup = new Rollup( + IFeeJuicePortal(address(0)), + IProofCommitmentEscrow(address(0)), + bytes32(0), + address(this), + new address[](0) + ); registry.upgrade(address(rollup)); daiTokenPortal = new TokenPortal(); diff --git a/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol b/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol index 401fd67a2bc0..3d0bdc529e42 100644 --- a/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol +++ b/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol @@ -8,7 +8,7 @@ import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Timestamp} from "@aztec/core/libraries/TimeMath.sol"; -import {TestERC20} from "../TestERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; // solhint-disable comprehensive-interface diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index a6e290c2fc1f..b222961537a8 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -15,9 +15,10 @@ import {Rollup} from "@aztec/core/Rollup.sol"; import {Leonidas} from "@aztec/core/Leonidas.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; -import {TestERC20} from "../TestERC20.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; +import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; import {Slot, Epoch, SlotLib, EpochLib} from "@aztec/core/libraries/TimeMath.sol"; @@ -74,7 +75,13 @@ contract SpartaTest is DecoderBase { } testERC20 = new TestERC20(); - rollup = new Rollup(IFeeJuicePortal(address(0)), bytes32(0), address(this), initialValidators); + rollup = new Rollup( + IFeeJuicePortal(address(0)), + IProofCommitmentEscrow(address(0)), + bytes32(0), + address(this), + initialValidators + ); inbox = Inbox(address(rollup.INBOX())); outbox = Outbox(address(rollup.OUTBOX())); diff --git a/yarn-project/aztec/src/sandbox.ts b/yarn-project/aztec/src/sandbox.ts index 9e508647a4bb..121875bdf3ae 100644 --- a/yarn-project/aztec/src/sandbox.ts +++ b/yarn-project/aztec/src/sandbox.ts @@ -18,6 +18,8 @@ import { FeeJuicePortalBytecode, InboxAbi, InboxBytecode, + MockProofCommitmentEscrowAbi, + MockProofCommitmentEscrowBytecode, OutboxAbi, OutboxBytecode, RegistryAbi, @@ -115,6 +117,10 @@ export async function deployContractsToL1( contractAbi: FeeJuicePortalAbi, contractBytecode: FeeJuicePortalBytecode, }, + proofCommitmentEscrow: { + contractAbi: MockProofCommitmentEscrowAbi, + contractBytecode: MockProofCommitmentEscrowBytecode, + }, }; const chain = aztecNodeConfig.l1RpcUrl diff --git a/yarn-project/cli/src/utils/aztec.ts b/yarn-project/cli/src/utils/aztec.ts index 2c6f4316ee25..da0260c88b65 100644 --- a/yarn-project/cli/src/utils/aztec.ts +++ b/yarn-project/cli/src/utils/aztec.ts @@ -69,7 +69,8 @@ export async function deployAztecContracts( RegistryBytecode, RollupAbi, RollupBytecode, - + MockProofCommitmentEscrowAbi, + MockProofCommitmentEscrowBytecode, FeeJuicePortalAbi, FeeJuicePortalBytecode, TestERC20Abi, @@ -107,6 +108,10 @@ export async function deployAztecContracts( contractAbi: FeeJuicePortalAbi, contractBytecode: FeeJuicePortalBytecode, }, + proofCommitmentEscrow: { + contractAbi: MockProofCommitmentEscrowAbi, + contractBytecode: MockProofCommitmentEscrowBytecode, + }, }; const { getVKTreeRoot } = await import('@aztec/noir-protocol-circuits-types'); diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index ba5f1c0f4bfa..ca6b956d067b 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -24,7 +24,8 @@ import { type UltraKeccakHonkProtocolArtifact, } from '@aztec/bb-prover'; import { compileContract } from '@aztec/ethereum'; -import { RollupAbi } from '@aztec/l1-artifacts'; +import { Buffer32 } from '@aztec/foundation/buffer'; +import { RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import { TokenContract } from '@aztec/noir-contracts.js'; import { type ProverNode, type ProverNodeConfig, createProverNode } from '@aztec/prover-node'; import { type PXEService } from '@aztec/pxe'; @@ -33,7 +34,8 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; // TODO(#7373): Deploy honk solidity verifier // @ts-expect-error solc-js doesn't publish its types https://github.com/ethereum/solc-js/issues/689 import solc from 'solc'; -import { getContract } from 'viem'; +import { type Hex, getContract } from 'viem'; +import { privateKeyToAddress } from 'viem/accounts'; import { waitRegisteredAccountSynced } from '../benchmarks/utils.js'; import { getACVMConfig } from '../fixtures/get_acvm_config.js'; @@ -94,7 +96,7 @@ export class FullProverTest { `full_prover_integration/${testName}`, dataPath, {}, - { assumeProvenThrough: undefined }, + { assumeProvenThrough: undefined, useRealProofCommitmentEscrow: true }, ); } @@ -258,6 +260,10 @@ export class FullProverTest { // The simulated prover node (now shutdown) used private key index 2 const proverNodePrivateKey = getPrivateKeyFromIndex(2); + const proverNodeSenderAddress = privateKeyToAddress(new Buffer32(proverNodePrivateKey!).to0xString()); + + this.logger.verbose(`Funding prover node at ${proverNodeSenderAddress}`); + await this.mintL1ERC20(proverNodeSenderAddress, 100_000_000n); this.logger.verbose('Starting prover node'); const proverConfig: ProverNodeConfig = { @@ -272,6 +278,8 @@ export class FullProverTest { proverNodePollingIntervalMs: 100, quoteProviderBasisPointFee: 100, quoteProviderBondAmount: 1000n, + proverMinimumStakeAmount: 3000n, + proverTargetStakeAmount: 6000n, }; this.proverNode = await createProverNode(proverConfig, { aztecNodeTxProvider: this.aztecNode, @@ -283,6 +291,14 @@ export class FullProverTest { return this; } + private async mintL1ERC20(recipient: Hex, amount: bigint) { + const erc20Address = this.context.deployL1ContractsValues.l1ContractAddresses.feeJuiceAddress; + const client = this.context.deployL1ContractsValues.walletClient; + const erc20 = getContract({ abi: TestERC20Abi, address: erc20Address.toString(), client }); + const hash = await erc20.write.mint([recipient, amount]); + await this.context.deployL1ContractsValues.publicClient.waitForTransactionReceipt({ hash }); + } + snapshot = ( name: string, apply: (context: SubsystemsContext) => Promise, diff --git a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts index c62950d85cbe..8ac08664c4c6 100644 --- a/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts +++ b/yarn-project/end-to-end/src/fixtures/setup_l1_contracts.ts @@ -5,8 +5,12 @@ import { FeeJuicePortalBytecode, InboxAbi, InboxBytecode, + MockProofCommitmentEscrowAbi, + MockProofCommitmentEscrowBytecode, OutboxAbi, OutboxBytecode, + ProofCommitmentEscrowAbi, + ProofCommitmentEscrowBytecode, RegistryAbi, RegistryBytecode, RollupAbi, @@ -26,7 +30,7 @@ export const setupL1Contracts = async ( l1RpcUrl: string, account: HDAccount | PrivateKeyAccount, logger: DebugLogger, - args: Pick, + args: Pick, ) => { const l1Artifacts: L1ContractArtifactsForDeployment = { registry: { @@ -53,6 +57,12 @@ export const setupL1Contracts = async ( contractAbi: FeeJuicePortalAbi, contractBytecode: FeeJuicePortalBytecode, }, + proofCommitmentEscrow: { + contractAbi: args.useRealProofCommitmentEscrow ? ProofCommitmentEscrowAbi : MockProofCommitmentEscrowAbi, + contractBytecode: args.useRealProofCommitmentEscrow + ? ProofCommitmentEscrowBytecode + : MockProofCommitmentEscrowBytecode, + }, }; const l1Data = await deployL1Contracts(l1RpcUrl, account, foundry, logger, l1Artifacts, { diff --git a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts index 1972e17960e3..27865ab6dd5c 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -273,6 +273,8 @@ export async function createAndSyncProverNode( proverNodePollingIntervalMs: 200, quoteProviderBasisPointFee: 100, quoteProviderBondAmount: 1000n, + proverMinimumStakeAmount: 0n, + proverTargetStakeAmount: 0n, }; const proverNode = await createProverNode(proverConfig, { aztecNodeTxProvider: aztecNode, diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index d13ba870ca2a..097064176c05 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -47,8 +47,12 @@ import { FeeJuicePortalBytecode, InboxAbi, InboxBytecode, + MockProofCommitmentEscrowAbi, + MockProofCommitmentEscrowBytecode, OutboxAbi, OutboxBytecode, + ProofCommitmentEscrowAbi, + ProofCommitmentEscrowBytecode, RegistryAbi, RegistryBytecode, RollupAbi, @@ -113,7 +117,12 @@ export const setupL1Contracts = async ( l1RpcUrl: string, account: HDAccount | PrivateKeyAccount, logger: DebugLogger, - args: { salt?: number; initialValidators?: EthAddress[]; assumeProvenThrough?: number } = { + args: { + salt?: number; + initialValidators?: EthAddress[]; + assumeProvenThrough?: number; + useRealProofCommitmentEscrow?: boolean; + } = { assumeProvenThrough: Number.MAX_SAFE_INTEGER, }, chain: Chain = foundry, @@ -143,6 +152,12 @@ export const setupL1Contracts = async ( contractAbi: FeeJuicePortalAbi, contractBytecode: FeeJuicePortalBytecode, }, + proofCommitmentEscrow: { + contractAbi: args.useRealProofCommitmentEscrow ? ProofCommitmentEscrowAbi : MockProofCommitmentEscrowAbi, + contractBytecode: args.useRealProofCommitmentEscrow + ? ProofCommitmentEscrowBytecode + : MockProofCommitmentEscrowBytecode, + }, }; const l1Data = await deployL1Contracts(l1RpcUrl, account, chain, logger, l1Artifacts, { diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 6fe962560bc9..e30fbe8ea802 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -3,7 +3,7 @@ import { EthAddress } from '@aztec/foundation/eth-address'; import { type Fr } from '@aztec/foundation/fields'; import { type DebugLogger } from '@aztec/foundation/log'; -import type { Abi, Narrow } from 'abitype'; +import type { Abi, AbiConstructor, Narrow } from 'abitype'; import { type Account, type Chain, @@ -89,6 +89,10 @@ export interface L1ContractArtifactsForDeployment { * Fee juice portal contract artifacts. Optional for now as gas is not strictly enforced */ feeJuicePortal: ContractArtifacts; + /** + * Proof commitment escrow. Either mock or actual implementation. + */ + proofCommitmentEscrow: ContractArtifacts; } export interface DeployL1ContractsArgs { @@ -112,6 +116,10 @@ export interface DeployL1ContractsArgs { * The initial validators for the rollup contract. */ initialValidators?: EthAddress[]; + /** + * Whether to deploy the real proof commitment escrow as opposed to the mock. + */ + useRealProofCommitmentEscrow?: boolean; } export type L1Clients = { @@ -207,8 +215,19 @@ export const deployL1Contracts = async ( logger.info(`Deployed Gas Portal at ${feeJuicePortalAddress}`); + // Mock implementation of escrow takes no arguments + const proofCommitmentEscrow = await deployer.deploy( + contractsToDeploy.proofCommitmentEscrow, + (contractsToDeploy.proofCommitmentEscrow.contractAbi as Abi).find( + (fn): fn is AbiConstructor => fn.type === 'constructor', + )?.inputs.length === 0 + ? [] + : [feeJuiceAddress, account.address.toString()], + ); + const rollupAddress = await deployer.deploy(contractsToDeploy.rollup, [ getAddress(feeJuicePortalAddress.toString()), + proofCommitmentEscrow.toString(), args.vkTreeRoot.toString(), account.address.toString(), args.initialValidators?.map(v => v.toString()) ?? [], diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index d20de524be54..9f3457398473 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -105,6 +105,8 @@ export type EnvVar = | 'QUOTE_PROVIDER_BASIS_POINT_FEE' | 'QUOTE_PROVIDER_BOND_AMOUNT' | 'QUOTE_PROVIDER_URL' + | 'PROVER_TARGET_STAKE_AMOUNT' + | 'PROVER_MINIMUM_STAKE_AMOUNT' | 'REGISTRY_CONTRACT_ADDRESS' | 'ROLLUP_CONTRACT_ADDRESS' | 'SEQ_ALLOWED_SETUP_FN' diff --git a/yarn-project/foundation/src/config/index.ts b/yarn-project/foundation/src/config/index.ts index a075be3fccf7..48cbe0301a7c 100644 --- a/yarn-project/foundation/src/config/index.ts +++ b/yarn-project/foundation/src/config/index.ts @@ -72,7 +72,7 @@ export function numberConfigHelper(defaultVal: number): Pick { +export function bigintConfigHelper(defaultVal?: bigint): Pick { return { parseEnv: (val: string) => BigInt(val), defaultValue: defaultVal, diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index 2903b4ca4296..6728a894d1d5 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -21,6 +21,9 @@ CONTRACTS=( "l1-contracts:FeeJuicePortal" "l1-contracts:MockVerifier" "l1-contracts:IVerifier" + "l1-contracts:IProofCommitmentEscrow" + "l1-contracts:ProofCommitmentEscrow" + "l1-contracts:MockProofCommitmentEscrow" ) diff --git a/yarn-project/prover-node/src/bond/bond-manager.test.ts b/yarn-project/prover-node/src/bond/bond-manager.test.ts new file mode 100644 index 000000000000..da232dba7c10 --- /dev/null +++ b/yarn-project/prover-node/src/bond/bond-manager.test.ts @@ -0,0 +1,71 @@ +import { type MockProxy, mock } from 'jest-mock-extended'; + +import { BondManager } from '../bond/bond-manager.js'; +import { type EscrowContract } from '../bond/escrow-contract.js'; +import { type TokenContract } from '../bond/token-contract.js'; + +describe('BondManager', () => { + let bondManager: BondManager; + + let tokenContract: MockProxy; + let escrowContract: MockProxy; + + beforeEach(() => { + tokenContract = mock(); + escrowContract = mock(); + + tokenContract.getBalance.mockResolvedValue(10000n); + bondManager = new BondManager(tokenContract, escrowContract, 100n, 1000n); + }); + + it('ensures bond if current bond is below minimum', async () => { + escrowContract.getProverDeposit.mockResolvedValue(50n); + + await bondManager.ensureBond(); + + expect(escrowContract.getProverDeposit).toHaveBeenCalled(); + expect(tokenContract.ensureAllowance).toHaveBeenCalled(); + expect(escrowContract.depositProverBond).toHaveBeenCalledWith(950n); + }); + + it('does not ensure bond if current bond is above minimum', async () => { + escrowContract.getProverDeposit.mockResolvedValue(200n); + + await bondManager.ensureBond(); + + expect(escrowContract.getProverDeposit).toHaveBeenCalled(); + expect(tokenContract.ensureAllowance).not.toHaveBeenCalled(); + expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); + }); + + it('throws error if not enough balance to top-up bond', async () => { + escrowContract.getProverDeposit.mockResolvedValue(50n); + tokenContract.getBalance.mockResolvedValue(100n); + + await expect(bondManager.ensureBond()).rejects.toThrow(/Not enough balance/i); + }); + + it('overrides minimum bond threshold', async () => { + escrowContract.getProverDeposit.mockResolvedValue(150n); + + await bondManager.ensureBond(); + expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); + + await bondManager.ensureBond(200n); + expect(escrowContract.getProverDeposit).toHaveBeenCalled(); + expect(tokenContract.ensureAllowance).toHaveBeenCalled(); + expect(escrowContract.depositProverBond).toHaveBeenCalledWith(850n); + }); + + it('overrides target bond threshold', async () => { + escrowContract.getProverDeposit.mockResolvedValue(150n); + + await bondManager.ensureBond(); + expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); + + await bondManager.ensureBond(2000n); + expect(escrowContract.getProverDeposit).toHaveBeenCalled(); + expect(tokenContract.ensureAllowance).toHaveBeenCalled(); + expect(escrowContract.depositProverBond).toHaveBeenCalledWith(1850n); + }); +}); diff --git a/yarn-project/prover-node/src/bond/bond-manager.ts b/yarn-project/prover-node/src/bond/bond-manager.ts new file mode 100644 index 000000000000..d410fd0228eb --- /dev/null +++ b/yarn-project/prover-node/src/bond/bond-manager.ts @@ -0,0 +1,48 @@ +import { createDebugLogger } from '@aztec/foundation/log'; + +import { type EscrowContract } from './escrow-contract.js'; +import { type TokenContract } from './token-contract.js'; + +export class BondManager { + private logger = createDebugLogger('aztec:prover-node:bond-manager'); + + constructor( + private readonly tokenContract: TokenContract, + private readonly escrowContract: EscrowContract, + /** Minimum escrowed bond. A top-up will be issued once this threshold is hit. */ + public minimumAmount: bigint, + /** Target escrowed bond. Top-up will target this value. */ + public targetAmount: bigint, + ) {} + + /** + * Ensures the bond is at least minimumBond, or sends a tx to deposit the remaining to reach targetBond. + * @param overrideMinimum - Override the minimum bond threshold. Also overrides target if it is higher. + */ + public async ensureBond(overrideMinimum?: bigint) { + const minimum = overrideMinimum ?? this.minimumAmount; + const target = overrideMinimum && overrideMinimum > this.targetAmount ? overrideMinimum : this.targetAmount; + + try { + const current = await this.escrowContract.getProverDeposit(); + if (current > minimum) { + this.logger.debug(`Current prover bond ${current} is above minimum ${minimum}`); + return; + } + + const topUpAmount = target - current; + this.logger.verbose(`Prover bond top-up ${topUpAmount} required to get ${current} to target ${target}`); + + const balance = await this.tokenContract.getBalance(); + if (balance < topUpAmount) { + throw new Error(`Not enough balance to top-up prover bond: ${balance} < ${topUpAmount}`); + } + + await this.tokenContract.ensureAllowance(this.escrowContract.getSenderAddress()); + await this.escrowContract.depositProverBond(topUpAmount); + this.logger.verbose(`Prover bond top-up of ${topUpAmount} completed`); + } catch (err) { + throw new Error(`Could not set prover bond: ${err}`); + } + } +} diff --git a/yarn-project/prover-node/src/bond/config.ts b/yarn-project/prover-node/src/bond/config.ts new file mode 100644 index 000000000000..d6a465f89ba2 --- /dev/null +++ b/yarn-project/prover-node/src/bond/config.ts @@ -0,0 +1,25 @@ +import { type ConfigMappingsType, bigintConfigHelper, getConfigFromMappings } from '@aztec/foundation/config'; + +export type ProverBondManagerConfig = { + proverMinimumStakeAmount: bigint; + proverTargetStakeAmount?: bigint; +}; + +export const proverBondManagerConfigMappings: ConfigMappingsType = { + proverMinimumStakeAmount: { + env: 'PROVER_MINIMUM_STAKE_AMOUNT', + description: + 'Minimum amount to ensure is staked in the escrow contract for this prover. Prover node will top up whenever escrow falls below this number.', + ...bigintConfigHelper(100000n), + }, + proverTargetStakeAmount: { + env: 'PROVER_TARGET_STAKE_AMOUNT', + description: + 'Target amount to ensure is staked in the escrow contract for this prover. Prover node will top up to this value. Defaults to the minimum amount.', + ...bigintConfigHelper(), + }, +}; + +export function getProverBondManagerConfigFromEnv(): ProverBondManagerConfig { + return getConfigFromMappings(proverBondManagerConfigMappings); +} diff --git a/yarn-project/prover-node/src/bond/escrow-contract.ts b/yarn-project/prover-node/src/bond/escrow-contract.ts new file mode 100644 index 000000000000..79c5111e3270 --- /dev/null +++ b/yarn-project/prover-node/src/bond/escrow-contract.ts @@ -0,0 +1,59 @@ +import { EthAddress } from '@aztec/circuits.js'; +import { IProofCommitmentEscrowAbi } from '@aztec/l1-artifacts'; + +import { + type Chain, + type Client, + type GetContractReturnType, + type HttpTransport, + type PrivateKeyAccount, + type PublicActions, + type PublicRpcSchema, + type WalletActions, + type WalletClient, + type WalletRpcSchema, + getContract, +} from 'viem'; + +export class EscrowContract { + private escrow: GetContractReturnType< + typeof IProofCommitmentEscrowAbi, + WalletClient + >; + + constructor( + private readonly client: Client< + HttpTransport, + Chain, + PrivateKeyAccount, + [...WalletRpcSchema, ...PublicRpcSchema], + PublicActions & WalletActions + >, + address: EthAddress, + ) { + this.escrow = getContract({ address: address.toString(), abi: IProofCommitmentEscrowAbi, client }); + } + + /** Returns the deposit of the publisher sender address on the proof commitment escrow contract. */ + public async getProverDeposit() { + return await this.escrow.read.deposits([this.getSenderAddress().toString()]); + } + + /** + * Deposits the given amount of tokens into the proof commitment escrow contract. Returns once the tx is mined. + * @param amount - The amount to deposit. + */ + public async depositProverBond(amount: bigint) { + const hash = await this.escrow.write.deposit([amount]); + await this.client.waitForTransactionReceipt({ hash }); + } + + /** Returns the sender address for the client. */ + public getSenderAddress(): EthAddress { + return EthAddress.fromString(this.client.account.address); + } + + public async getTokenContractAddress(): Promise { + return EthAddress.fromString(await this.escrow.read.token()); + } +} diff --git a/yarn-project/prover-node/src/bond/factory.ts b/yarn-project/prover-node/src/bond/factory.ts new file mode 100644 index 000000000000..ffcb4cb5e486 --- /dev/null +++ b/yarn-project/prover-node/src/bond/factory.ts @@ -0,0 +1,44 @@ +import { EthAddress } from '@aztec/circuits.js'; +import { compact } from '@aztec/foundation/collection'; +import { type RollupAbi } from '@aztec/l1-artifacts'; + +import { + type Chain, + type Client, + type GetContractReturnType, + type HttpTransport, + type PrivateKeyAccount, + type PublicActions, + type PublicClient, + type PublicRpcSchema, + type WalletActions, + type WalletRpcSchema, +} from 'viem'; + +import { BondManager } from './bond-manager.js'; +import { type ProverBondManagerConfig, getProverBondManagerConfigFromEnv } from './config.js'; +import { EscrowContract } from './escrow-contract.js'; +import { TokenContract } from './token-contract.js'; + +export async function createBondManager( + rollupContract: GetContractReturnType, + client: Client< + HttpTransport, + Chain, + PrivateKeyAccount, + [...WalletRpcSchema, ...PublicRpcSchema], + PublicActions & WalletActions + >, + overrides: Partial = {}, +) { + const config = { ...getProverBondManagerConfigFromEnv(), ...compact(overrides) }; + const { proverMinimumStakeAmount: minimumStake, proverTargetStakeAmount: targetStake } = config; + + const escrowContractAddress = EthAddress.fromString(await rollupContract.read.PROOF_COMMITMENT_ESCROW()); + const escrow = new EscrowContract(client, escrowContractAddress); + + const tokenContractAddress = await escrow.getTokenContractAddress(); + const token = new TokenContract(client, tokenContractAddress); + + return new BondManager(token, escrow, minimumStake, targetStake ?? minimumStake); +} diff --git a/yarn-project/prover-node/src/bond/index.ts b/yarn-project/prover-node/src/bond/index.ts new file mode 100644 index 000000000000..0a1e5ba57fec --- /dev/null +++ b/yarn-project/prover-node/src/bond/index.ts @@ -0,0 +1,2 @@ +export { BondManager } from './bond-manager.js'; +export * from './factory.js'; diff --git a/yarn-project/prover-node/src/bond/token-contract.ts b/yarn-project/prover-node/src/bond/token-contract.ts new file mode 100644 index 000000000000..f9b89094b4d1 --- /dev/null +++ b/yarn-project/prover-node/src/bond/token-contract.ts @@ -0,0 +1,62 @@ +import { EthAddress } from '@aztec/circuits.js'; +import { createDebugLogger } from '@aztec/foundation/log'; +import { IERC20Abi } from '@aztec/l1-artifacts'; + +import { + type Chain, + type Client, + type GetContractReturnType, + type HttpTransport, + type PrivateKeyAccount, + type PublicActions, + type PublicRpcSchema, + type WalletActions, + type WalletClient, + type WalletRpcSchema, + getContract, +} from 'viem'; + +const MAX_ALLOWANCE = (1n << 256n) - 1n; +const MIN_ALLOWANCE = 1n << 255n; + +export class TokenContract { + private token: GetContractReturnType>; + private logger = createDebugLogger('aztec:prover-node:token-contract'); + + constructor( + private readonly client: Client< + HttpTransport, + Chain, + PrivateKeyAccount, + [...WalletRpcSchema, ...PublicRpcSchema], + PublicActions & WalletActions + >, + address: EthAddress, + ) { + this.token = getContract({ address: address.toString(), abi: IERC20Abi, client }); + } + + /** + * Ensures the allowed address has near-maximum allowance, or sets it otherwise. + * Returns once allowance tx is mined successfully. + * @param allowed - Who to allow. + */ + public async ensureAllowance(allowed: EthAddress) { + const allowance = await this.token.read.allowance([this.getSenderAddress().toString(), allowed.toString()]); + if (allowance < MIN_ALLOWANCE) { + this.logger.verbose(`Approving max allowance for ${allowed.toString()}`); + const hash = await this.token.write.approve([allowed.toString(), MAX_ALLOWANCE]); + await this.client.waitForTransactionReceipt({ hash }); + } + } + + /** Returns the sender address. */ + public getSenderAddress(): EthAddress { + return EthAddress.fromString(this.client.account.address); + } + + /** Returns the balance of the sender. */ + public async getBalance() { + return await this.token.read.balanceOf([this.getSenderAddress().toString()]); + } +} diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts index 2a4d5a99ec5b..8049b0d494e0 100644 --- a/yarn-project/prover-node/src/config.ts +++ b/yarn-project/prover-node/src/config.ts @@ -16,6 +16,7 @@ import { } from '@aztec/sequencer-client'; import { type WorldStateConfig, getWorldStateConfigFromEnv, worldStateConfigMappings } from '@aztec/world-state'; +import { type ProverBondManagerConfig, proverBondManagerConfigMappings } from './bond/config.js'; import { type ProverCoordinationConfig, getTxProviderConfigFromEnv, @@ -28,6 +29,7 @@ export type ProverNodeConfig = ArchiverConfig & PublisherConfig & TxSenderConfig & ProverCoordinationConfig & + ProverBondManagerConfig & QuoteProviderConfig & { proverNodeMaxPendingJobs: number; proverNodePollingIntervalMs: number; @@ -80,6 +82,7 @@ export const proverNodeConfigMappings: ConfigMappingsType = { ...getTxSenderConfigMappings('PROVER'), ...proverCoordinationConfigMappings, ...quoteProviderConfigMappings, + ...proverBondManagerConfigMappings, ...specificProverNodeConfigMappings, }; @@ -93,5 +96,6 @@ export function getProverNodeConfigFromEnv(): ProverNodeConfig { ...getTxProviderConfigFromEnv(), ...getConfigFromMappings(quoteProviderConfigMappings), ...getConfigFromMappings(specificProverNodeConfigMappings), + ...getConfigFromMappings(proverBondManagerConfigMappings), }; } diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 52b4431a9585..6d376676761e 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -13,6 +13,7 @@ import { createWorldStateSynchronizer } from '@aztec/world-state'; import { createPublicClient, getAddress, getContract, http } from 'viem'; +import { createBondManager } from './bond/factory.js'; import { type ProverNodeConfig, type QuoteProviderConfig } from './config.js'; import { ClaimsMonitor } from './monitors/claims-monitor.js'; import { EpochMonitor } from './monitors/epoch-monitor.js'; @@ -60,6 +61,10 @@ export async function createProverNode( const claimsMonitor = new ClaimsMonitor(publisher, proverNodeConfig); const epochMonitor = new EpochMonitor(archiver, proverNodeConfig); + const rollupContract = publisher.getRollupContract(); + const walletClient = publisher.getClient(); + const bondManager = await createBondManager(rollupContract, walletClient, config); + return new ProverNode( prover!, publisher, @@ -73,6 +78,7 @@ export async function createProverNode( quoteSigner, claimsMonitor, epochMonitor, + bondManager, telemetry, proverNodeConfig, ); diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 5e764ed73b4e..55ba45781396 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -22,6 +22,7 @@ import { type ContractDataSource } from '@aztec/types/contracts'; import { type MockProxy, mock } from 'jest-mock-extended'; +import { type BondManager } from './bond/bond-manager.js'; import { type EpochProvingJob } from './job/epoch-proving-job.js'; import { ClaimsMonitor } from './monitors/claims-monitor.js'; import { EpochMonitor } from './monitors/epoch-monitor.js'; @@ -41,6 +42,7 @@ describe('prover-node', () => { let simulator: MockProxy; let quoteProvider: MockProxy; let quoteSigner: MockProxy; + let bondManager: MockProxy; let telemetryClient: NoopTelemetryClient; let config: ProverNodeOptions; @@ -77,6 +79,25 @@ describe('prover-node', () => { quote: Pick = partialQuote, ) => expect.objectContaining({ payload: toQuotePayload(epoch, quote) }); + const createProverNode = (claimsMonitor: ClaimsMonitor, epochMonitor: EpochMonitor) => + new TestProverNode( + prover, + publisher, + l2BlockSource, + l1ToL2MessageSource, + contractDataSource, + worldState, + coordination, + simulator, + quoteProvider, + quoteSigner, + claimsMonitor, + epochMonitor, + bondManager, + telemetryClient, + config, + ); + beforeEach(() => { prover = mock(); publisher = mock(); @@ -88,6 +109,7 @@ describe('prover-node', () => { simulator = mock(); quoteProvider = mock(); quoteSigner = mock(); + bondManager = mock(); telemetryClient = new NoopTelemetryClient(); config = { maxPendingJobs: 3, pollingIntervalMs: 10 }; @@ -129,22 +151,7 @@ describe('prover-node', () => { claimsMonitor = mock(); epochMonitor = mock(); - proverNode = new TestProverNode( - prover, - publisher, - l2BlockSource, - l1ToL2MessageSource, - contractDataSource, - worldState, - coordination, - simulator, - quoteProvider, - quoteSigner, - claimsMonitor, - epochMonitor, - telemetryClient, - config, - ); + proverNode = createProverNode(claimsMonitor, epochMonitor); }); it('sends a quote on a finished epoch', async () => { @@ -240,22 +247,7 @@ describe('prover-node', () => { Promise.resolve(epochNumber <= lastEpochComplete), ); - proverNode = new TestProverNode( - prover, - publisher, - l2BlockSource, - l1ToL2MessageSource, - contractDataSource, - worldState, - coordination, - simulator, - quoteProvider, - quoteSigner, - claimsMonitor, - epochMonitor, - telemetryClient, - config, - ); + proverNode = createProverNode(claimsMonitor, epochMonitor); }); it('sends a quote on initial sync', async () => { diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 19a67341c11b..353a71a65006 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -17,6 +17,7 @@ import { PublicProcessorFactory, type SimulationProvider } from '@aztec/simulato import { type TelemetryClient } from '@aztec/telemetry-client'; import { type ContractDataSource } from '@aztec/types/contracts'; +import { type BondManager } from './bond/bond-manager.js'; import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-job.js'; import { ProverNodeMetrics } from './metrics.js'; import { type ClaimsMonitor, type ClaimsMonitorHandler } from './monitors/claims-monitor.js'; @@ -56,6 +57,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { private readonly quoteSigner: QuoteSigner, private readonly claimsMonitor: ClaimsMonitor, private readonly epochsMonitor: EpochMonitor, + private readonly bondManager: BondManager, private readonly telemetryClient: TelemetryClient, options: Partial = {}, ) { @@ -68,12 +70,6 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { this.metrics = new ProverNodeMetrics(telemetryClient, 'ProverNode'); } - async ensureBond() { - // Ensure the prover has enough bond to submit proofs - // Can we just run this at the beginning and forget about it? - // Or do we need to check periodically? Or only when we get slashed? How do we know we got slashed? - } - async handleClaim(proofClaim: EpochProofClaim): Promise { if (proofClaim.epochToProve === this.latestEpochWeAreProving) { this.log.verbose(`Already proving claim for epoch ${proofClaim.epochToProve}`); @@ -86,6 +82,13 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { } catch (err) { this.log.error(`Error handling claim for epoch ${proofClaim.epochToProve}`, err); } + + try { + // Staked amounts are lowered after a claim, so this is a good time for doing a top-up if needed + await this.bondManager.ensureBond(); + } catch (err) { + this.log.error(`Error ensuring prover bond after handling claim for epoch ${proofClaim.epochToProve}`, err); + } } /** @@ -115,6 +118,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { */ async handleEpochCompleted(epochNumber: bigint): Promise { try { + // Construct a quote for the epoch const blocks = await this.l2BlockSource.getBlocksForEpoch(epochNumber); const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks); if (!partialQuote) { @@ -122,6 +126,10 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { return; } + // Ensure we have deposited enough funds for sending this quote + await this.bondManager.ensureBond(partialQuote.bondAmount); + + // Assemble and sign full quote const quote = EpochProofQuotePayload.from({ ...partialQuote, epochToProve: BigInt(epochNumber), @@ -129,6 +137,8 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER), // Should we constrain this? }); const signed = await this.quoteSigner.sign(quote); + + // Send it to the coordinator await this.sendEpochProofQuote(signed); } catch (err) { this.log.error(`Error handling epoch completed`, err); @@ -136,11 +146,12 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler { } /** - * Starts the prover node so it periodically checks for unproven blocks in the unfinalised chain from L1 and proves them. - * This may change once we implement a prover coordination mechanism. + * Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and sends + * quotes for them, as well as monitors the claims for the epochs it has sent quotes for and starts proving jobs. + * This method returns once the prover node has deposited an initial bond into the escrow contract. */ async start() { - await this.ensureBond(); + await this.bondManager.ensureBond(); this.epochsMonitor.start(this); this.claimsMonitor.start(this); this.log.info('Started ProverNode', this.options); diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 79ea85ffae0f..adbf05e29dd1 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -32,13 +32,19 @@ import pick from 'lodash.pick'; import { inspect } from 'util'; import { type BaseError, + type Chain, + type Client, ContractFunctionRevertedError, type GetContractReturnType, type Hex, type HttpTransport, type PrivateKeyAccount, + type PublicActions, type PublicClient, + type PublicRpcSchema, + type WalletActions, type WalletClient, + type WalletRpcSchema, createPublicClient, createWalletClient, encodeFunctionData, @@ -47,6 +53,7 @@ import { getContract, hexToBytes, http, + publicActions, } from 'viem'; import { privateKeyToAccount } from 'viem/accounts'; import type * as chains from 'viem/chains'; @@ -132,7 +139,9 @@ export class L1Publisher { typeof RollupAbi, WalletClient >; + private publicClient: PublicClient; + private walletClient: WalletClient; private account: PrivateKeyAccount; public static PROPOSE_GAS_GUESS: bigint = 500_000n; @@ -146,7 +155,8 @@ export class L1Publisher { const chain = createEthereumChain(rpcUrl, chainId); this.account = privateKeyToAccount(publisherPrivateKey); this.log.debug(`Publishing from address ${this.account.address}`); - const walletClient = createWalletClient({ + + this.walletClient = createWalletClient({ account: this.account, chain: chain.chainInfo, transport: http(chain.rpcUrl), @@ -160,7 +170,7 @@ export class L1Publisher { this.rollupContract = getContract({ address: getAddress(l1Contracts.rollupAddress.toString()), abi: RollupAbi, - client: walletClient, + client: this.walletClient, }); } @@ -168,6 +178,23 @@ export class L1Publisher { return EthAddress.fromString(this.account.address); } + public getClient(): Client< + HttpTransport, + Chain, + PrivateKeyAccount, + [...WalletRpcSchema, ...PublicRpcSchema], + PublicActions & WalletActions + > { + return this.walletClient.extend(publicActions); + } + + public getRollupContract(): GetContractReturnType< + typeof RollupAbi, + WalletClient + > { + return this.rollupContract; + } + /** * @notice Calls `canProposeAtTime` with the time of the next Ethereum block and the sender address *