From 3f8f5d1f8bac098cff4cf684f7c943cc1ca88856 Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 17 Sep 2024 15:55:34 -0400 Subject: [PATCH 01/43] define escrow contract interface implement mock escrow and include in rollup move signature lib --- l1-contracts/src/core/Rollup.sol | 6 +++- .../interfaces/IProofCommitmentEscrow.sol | 13 +++++++++ l1-contracts/src/core/interfaces/IRollup.sol | 2 +- .../src/core/libraries/DataStructures.sol | 11 +++++++ .../SignatureLib.sol | 4 ++- .../src/core/sequencer_selection/Leonidas.sol | 2 +- .../src/mock/MockProofCommitmentEscrow.sol | 29 +++++++++++++++++++ l1-contracts/test/Rollup.t.sol | 2 +- l1-contracts/test/sparta/Sparta.t.sol | 2 +- 9 files changed, 65 insertions(+), 6 deletions(-) create mode 100644 l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol rename l1-contracts/src/core/{sequencer_selection => libraries}/SignatureLib.sol (88%) create mode 100644 l1-contracts/src/mock/MockProofCommitmentEscrow.sol diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 283aafc4828..e7e025a12ac 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.18; // Interfaces import {IRollup, ITestRollup} from "./interfaces/IRollup.sol"; +import {IProofCommitmentEscrow} from "./interfaces/IProofCommitmentEscrow.sol"; import {IInbox} from "./interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "./interfaces/messagebridge/IOutbox.sol"; import {IRegistry} from "./interfaces/messagebridge/IRegistry.sol"; @@ -15,13 +16,14 @@ import {HeaderLib} from "./libraries/HeaderLib.sol"; import {Errors} from "./libraries/Errors.sol"; import {Constants} from "./libraries/ConstantsGen.sol"; import {MerkleLib} from "./libraries/MerkleLib.sol"; -import {SignatureLib} from "./sequencer_selection/SignatureLib.sol"; +import {SignatureLib} from "./libraries/SignatureLib.sol"; import {SafeCast} from "@oz/utils/math/SafeCast.sol"; import {DataStructures} from "./libraries/DataStructures.sol"; import {TxsDecoder} from "./libraries/decoders/TxsDecoder.sol"; // Contracts import {MockVerifier} from "../mock/MockVerifier.sol"; +import {MockProofCommitmentEscrow} from "../mock/MockProofCommitmentEscrow.sol"; import {Inbox} from "./messagebridge/Inbox.sol"; import {Outbox} from "./messagebridge/Outbox.sol"; import {Leonidas} from "./sequencer_selection/Leonidas.sol"; @@ -55,6 +57,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { IRegistry public immutable REGISTRY; IInbox public immutable INBOX; IOutbox public immutable OUTBOX; + IProofCommitmentEscrow public immutable PROOF_COMMITMENT_ESCROW; uint256 public immutable VERSION; IFeeJuicePortal public immutable FEE_JUICE_PORTAL; IVerifier public verifier; @@ -84,6 +87,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { verifier = new MockVerifier(); REGISTRY = _registry; FEE_JUICE_PORTAL = _fpcJuicePortal; + PROOF_COMMITMENT_ESCROW = new MockProofCommitmentEscrow(); INBOX = new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT); OUTBOX = new Outbox(address(this)); vkTreeRoot = _vkTreeRoot; diff --git a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol new file mode 100644 index 00000000000..8ef52a2e670 --- /dev/null +++ b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.18; + +import {DataStructures} from "../libraries/DataStructures.sol"; + +interface IProofCommitmentEscrow { + function deposit(uint256 _amount) external; + function withdraw(uint256 _amount) external; + // returns the address of the bond provider + function stakeBond(DataStructures.EpochProofQuote calldata _quote) external returns (address); + function unstakeBond(uint256 _amount) external; +} diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 1f02808bf96..4fe5e30dab2 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.18; import {IInbox} from "../interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "../interfaces/messagebridge/IOutbox.sol"; -import {SignatureLib} from "../sequencer_selection/SignatureLib.sol"; +import {SignatureLib} from "../libraries/SignatureLib.sol"; import {DataStructures} from "../libraries/DataStructures.sol"; interface ITestRollup { diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 5462e09fa1b..2b6438c3702 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -2,6 +2,8 @@ // Copyright 2023 Aztec Labs. pragma solidity >=0.8.18; +import {SignatureLib} from "./SignatureLib.sol"; + /** * @title Data Structures Library * @author Aztec Labs @@ -79,4 +81,13 @@ library DataStructures { bool ignoreDA; bool ignoreSignatures; } + + struct EpochProofQuote { + SignatureLib.Signature signature; + uint256 epochToProve; + uint256 validUntilSlot; + uint256 bondAmount; + address rollup; + uint32 basisPointFee; + } } diff --git a/l1-contracts/src/core/sequencer_selection/SignatureLib.sol b/l1-contracts/src/core/libraries/SignatureLib.sol similarity index 88% rename from l1-contracts/src/core/sequencer_selection/SignatureLib.sol rename to l1-contracts/src/core/libraries/SignatureLib.sol index 434e6945c3f..1bb8d6fb050 100644 --- a/l1-contracts/src/core/sequencer_selection/SignatureLib.sol +++ b/l1-contracts/src/core/libraries/SignatureLib.sol @@ -1,6 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. pragma solidity ^0.8.13; -import {Errors} from "../libraries/Errors.sol"; +import {Errors} from "./Errors.sol"; library SignatureLib { struct Signature { diff --git a/l1-contracts/src/core/sequencer_selection/Leonidas.sol b/l1-contracts/src/core/sequencer_selection/Leonidas.sol index 48cbaef0579..57438f6860a 100644 --- a/l1-contracts/src/core/sequencer_selection/Leonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/Leonidas.sol @@ -6,8 +6,8 @@ import {DataStructures} from "../libraries/DataStructures.sol"; import {Errors} from "../libraries/Errors.sol"; import {EnumerableSet} from "@oz/utils/structs/EnumerableSet.sol"; import {Ownable} from "@oz/access/Ownable.sol"; -import {SignatureLib} from "./SignatureLib.sol"; import {SampleLib} from "./SampleLib.sol"; +import {SignatureLib} from "../libraries/SignatureLib.sol"; import {Constants} from "../libraries/ConstantsGen.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; diff --git a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol new file mode 100644 index 00000000000..d7bc1d847e4 --- /dev/null +++ b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.18; + +import {DataStructures} from "../core/libraries/DataStructures.sol"; +import {IProofCommitmentEscrow} from "../core/interfaces/IProofCommitmentEscrow.sol"; + +contract MockProofCommitmentEscrow is IProofCommitmentEscrow { + function deposit(uint256 _amount) external override { + // do nothing + } + + function withdraw(uint256 _amount) external override { + // do nothing + } + + function unstakeBond(uint256 _amount) external override { + // do nothing + } + + function stakeBond(DataStructures.EpochProofQuote calldata) + external + pure + override + returns (address) + { + return address(0); + } +} diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 807d4563b05..084870f412d 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -6,6 +6,7 @@ import {DecoderBase} from "./decoders/Base.sol"; import {DataStructures} from "../src/core/libraries/DataStructures.sol"; import {Constants} from "../src/core/libraries/ConstantsGen.sol"; +import {SignatureLib} from "../src/core/libraries/SignatureLib.sol"; import {Registry} from "../src/core/messagebridge/Registry.sol"; import {Inbox} from "../src/core/messagebridge/Inbox.sol"; @@ -15,7 +16,6 @@ import {Rollup} from "../src/core/Rollup.sol"; import {IFeeJuicePortal} from "../src/core/interfaces/IFeeJuicePortal.sol"; import {FeeJuicePortal} from "../src/core/FeeJuicePortal.sol"; import {Leonidas} from "../src/core/sequencer_selection/Leonidas.sol"; -import {SignatureLib} from "../src/core/sequencer_selection/SignatureLib.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; import {MerkleTestUtil} from "./merkle/TestUtil.sol"; import {PortalERC20} from "./portals/PortalERC20.sol"; diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 3454245ffbf..9a8e84d143b 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -6,7 +6,7 @@ import {DecoderBase} from "../decoders/Base.sol"; import {DataStructures} from "../../src/core/libraries/DataStructures.sol"; import {Constants} from "../../src/core/libraries/ConstantsGen.sol"; -import {SignatureLib} from "../../src/core/sequencer_selection/SignatureLib.sol"; +import {SignatureLib} from "../../src/core/libraries/SignatureLib.sol"; import {Registry} from "../../src/core/messagebridge/Registry.sol"; import {Inbox} from "../../src/core/messagebridge/Inbox.sol"; From a567dc3b276b1af78cad944ee5fb247ab8a31077 Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 17 Sep 2024 19:20:04 -0400 Subject: [PATCH 02/43] prune if needed before propose and prove. --- l1-contracts/src/core/Rollup.sol | 61 ++++--- l1-contracts/src/core/libraries/Errors.sol | 1 - .../core/sequencer_selection/ILeonidas.sol | 3 + .../src/core/sequencer_selection/Leonidas.sol | 160 +++++++++--------- l1-contracts/test/Rollup.t.sol | 10 +- l1-contracts/test/sparta/Sparta.t.sol | 22 +-- 6 files changed, 136 insertions(+), 121 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index e7e025a12ac..71a7f7f0f2b 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -131,32 +131,13 @@ contract Rollup is Leonidas, IRollup, ITestRollup { * @notice Prune the pending chain up to the last proven block * * @dev Will revert if there is nothing to prune or if the chain is not ready to be pruned - * - * @dev While in devnet, this will be guarded behind an `onlyOwner` */ - function prune() external override(IRollup) onlyOwner { - if (tips.pendingBlockNumber == tips.provenBlockNumber) { + function prune() external override(IRollup) { + if (!_canPrune()) { revert Errors.Rollup__NothingToPrune(); } - BlockLog storage firstPendingNotInProven = blocks[tips.provenBlockNumber + 1]; - uint256 prunableAtSlot = - uint256(firstPendingNotInProven.slotNumber) + TIMELINESS_PROVING_IN_SLOTS; - uint256 currentSlot = getCurrentSlot(); - - if (currentSlot < prunableAtSlot) { - revert Errors.Rollup__NotReadyToPrune(currentSlot, prunableAtSlot); - } - - uint256 pending = tips.pendingBlockNumber; - - // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. - // We can do because any new block proposed will overwrite a previous block in the block log, - // so no values should "survive". - // People must therefore read the chain using the pendingTip as a boundary. - tips.pendingBlockNumber = tips.provenBlockNumber; - - emit PrunedPending(tips.provenBlockNumber, pending); + _prune(); } /** @@ -214,6 +195,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup { SignatureLib.Signature[] memory _signatures, bytes calldata _body ) external override(IRollup) { + if (_canPrune()) { + _prune(); + } bytes32 txsEffectsHash = TxsDecoder.decode(_body); // Decode and validate header @@ -298,6 +282,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes calldata _aggregationObject, bytes calldata _proof ) external override(IRollup) { + if (_canPrune()) { + _prune(); + } HeaderLib.Header memory header = HeaderLib.decode(_header); if (header.globalVariables.blockNumber > tips.pendingBlockNumber) { @@ -493,6 +480,36 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + function _prune() internal { + uint256 pending = tips.pendingBlockNumber; + + // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. + // We can do because any new block proposed will overwrite a previous block in the block log, + // so no values should "survive". + // People must therefore read the chain using the pendingTip as a boundary. + tips.pendingBlockNumber = tips.provenBlockNumber; + + emit PrunedPending(tips.provenBlockNumber, pending); + } + + function _canPrune() internal view returns (bool) { + if (tips.pendingBlockNumber == tips.provenBlockNumber) { + return false; + } + + uint256 currentSlot = getCurrentSlot(); + uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); + uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; + + // TODO: #8608 adds a proof claim, which will allow us to prune the chain more aggressively. + // That is what will add a `CLAIM_DURATION` to the pruning logic. + if (currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION) { + return false; + } + + return true; + } + /** * @notice Get the archive root of a specific block * diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index db826807b5e..fab6b2bac33 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -60,7 +60,6 @@ library Errors { error Rollup__TimestampTooOld(); // 0x72ed9c81 error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3 error Rollup__NothingToPrune(); // 0x850defd3 - error Rollup__NotReadyToPrune(uint256 currentSlot, uint256 prunableAt); // 0x9fdf1614 error Rollup__NonSequentialProving(); // 0x1e5be132 // Registry diff --git a/l1-contracts/src/core/sequencer_selection/ILeonidas.sol b/l1-contracts/src/core/sequencer_selection/ILeonidas.sol index a9542975205..68a58572236 100644 --- a/l1-contracts/src/core/sequencer_selection/ILeonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/ILeonidas.sol @@ -27,4 +27,7 @@ interface ILeonidas { function getCurrentEpochCommittee() external view returns (address[] memory); function getEpochCommittee(uint256 _epoch) external view returns (address[] memory); function getValidators() external view returns (address[] memory); + + function getEpochAt(uint256 _ts) external view returns (uint256); + function getSlotAt(uint256 _ts) external view returns (uint256); } diff --git a/l1-contracts/src/core/sequencer_selection/Leonidas.sol b/l1-contracts/src/core/sequencer_selection/Leonidas.sol index 57438f6860a..1b9484658c6 100644 --- a/l1-contracts/src/core/sequencer_selection/Leonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/Leonidas.sol @@ -128,28 +128,6 @@ contract Leonidas is Ownable, ILeonidas { return epochs[_epoch].committee; } - function getCommitteeAt(uint256 _ts) internal view returns (address[] memory) { - uint256 epochNumber = getEpochAt(_ts); - Epoch storage epoch = epochs[epochNumber]; - - if (epoch.sampleSeed != 0) { - uint256 committeeSize = epoch.committee.length; - if (committeeSize == 0) { - return new address[](0); - } - return epoch.committee; - } - - // Allow anyone if there is no validator set - if (validatorSet.length() == 0) { - return new address[](0); - } - - // Emulate a sampling of the validators - uint256 sampleSeed = _getSampleSeed(epochNumber); - return _sampleValidators(sampleSeed); - } - /** * @notice Get the validator set for the current epoch * @return The validator set for the current epoch @@ -169,6 +147,28 @@ contract Leonidas is Ownable, ILeonidas { return validatorSet.values(); } + /** + * @notice Performs a setup of an epoch if needed. The setup will + * - Sample the validator set for the epoch + * - Set the seed for the epoch + * - Update the last seed + * + * @dev Since this is a reference optimising for simplicity, we store the actual validator set in the epoch structure. + * This is very heavy on gas, so start crying because the gas here will melt the poles + * https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp + */ + function setupEpoch() public override(ILeonidas) { + uint256 epochNumber = getCurrentEpoch(); + Epoch storage epoch = epochs[epochNumber]; + + if (epoch.sampleSeed == 0) { + epoch.sampleSeed = _getSampleSeed(epochNumber); + epoch.nextSeed = lastSeed = _computeNextSeed(epochNumber); + + epoch.committee = _sampleValidators(epoch.sampleSeed); + } + } + /** * @notice Get the number of validators in the validator set * @@ -198,28 +198,6 @@ contract Leonidas is Ownable, ILeonidas { return validatorSet.contains(_validator); } - /** - * @notice Performs a setup of an epoch if needed. The setup will - * - Sample the validator set for the epoch - * - Set the seed for the epoch - * - Update the last seed - * - * @dev Since this is a reference optimising for simplicity, we store the actual validator set in the epoch structure. - * This is very heavy on gas, so start crying because the gas here will melt the poles - * https://i.giphy.com/U1aN4HTfJ2SmgB2BBK.webp - */ - function setupEpoch() public override(ILeonidas) { - uint256 epochNumber = getCurrentEpoch(); - Epoch storage epoch = epochs[epochNumber]; - - if (epoch.sampleSeed == 0) { - epoch.sampleSeed = _getSampleSeed(epochNumber); - epoch.nextSeed = lastSeed = _computeNextSeed(epochNumber); - - epoch.committee = _sampleValidators(epoch.sampleSeed); - } - } - /** * @notice Get the current epoch number * @@ -314,6 +292,28 @@ contract Leonidas is Ownable, ILeonidas { return committee[_computeProposerIndex(epochNumber, slot, sampleSeed, committee.length)]; } + /** + * @notice Computes the epoch at a specific time + * + * @param _ts - The timestamp to compute the epoch for + * + * @return The computed epoch + */ + function getEpochAt(uint256 _ts) public view override(ILeonidas) returns (uint256) { + return _ts < GENESIS_TIME ? 0 : (_ts - GENESIS_TIME) / (EPOCH_DURATION * SLOT_DURATION); + } + + /** + * @notice Computes the slot at a specific time + * + * @param _ts - The timestamp to compute the slot for + * + * @return The computed slot + */ + function getSlotAt(uint256 _ts) public view override(ILeonidas) returns (uint256) { + return (_ts - GENESIS_TIME) / SLOT_DURATION; + } + /** * @notice Adds a validator to the set WITHOUT setting up the epoch * @param _validator - The validator to add @@ -322,6 +322,28 @@ contract Leonidas is Ownable, ILeonidas { validatorSet.add(_validator); } + function getCommitteeAt(uint256 _ts) internal view returns (address[] memory) { + uint256 epochNumber = getEpochAt(_ts); + Epoch storage epoch = epochs[epochNumber]; + + if (epoch.sampleSeed != 0) { + uint256 committeeSize = epoch.committee.length; + if (committeeSize == 0) { + return new address[](0); + } + return epoch.committee; + } + + // Allow anyone if there is no validator set + if (validatorSet.length() == 0) { + return new address[](0); + } + + // Emulate a sampling of the validators + uint256 sampleSeed = _getSampleSeed(epochNumber); + return _sampleValidators(sampleSeed); + } + /** * @notice Propose a pending block from the point-of-view of sequencer selection. Will: * - Setup the epoch if needed (if epoch committee is empty skips the rest) @@ -392,6 +414,20 @@ contract Leonidas is Ownable, ILeonidas { } } + /** + * @notice Computes the nextSeed for an epoch + * + * @dev We include the `_epoch` instead of using the randao directly to avoid issues with foundry testing + * where randao == 0. + * + * @param _epoch - The epoch to compute the seed for + * + * @return The computed seed + */ + function _computeNextSeed(uint256 _epoch) private view returns (uint256) { + return uint256(keccak256(abi.encode(_epoch, block.prevrandao))); + } + /** * @notice Samples a validator set for a specific epoch * @@ -452,42 +488,6 @@ contract Leonidas is Ownable, ILeonidas { return lastSeed; } - /** - * @notice Computes the epoch at a specific time - * - * @param _ts - The timestamp to compute the epoch for - * - * @return The computed epoch - */ - function getEpochAt(uint256 _ts) public view returns (uint256) { - return (_ts - GENESIS_TIME) / (EPOCH_DURATION * SLOT_DURATION); - } - - /** - * @notice Computes the slot at a specific time - * - * @param _ts - The timestamp to compute the slot for - * - * @return The computed slot - */ - function getSlotAt(uint256 _ts) public view returns (uint256) { - return (_ts - GENESIS_TIME) / SLOT_DURATION; - } - - /** - * @notice Computes the nextSeed for an epoch - * - * @dev We include the `_epoch` instead of using the randao directly to avoid issues with foundry testing - * where randao == 0. - * - * @param _epoch - The epoch to compute the seed for - * - * @return The computed seed - */ - function _computeNextSeed(uint256 _epoch) private view returns (uint256) { - return uint256(keccak256(abi.encode(_epoch, block.prevrandao))); - } - /** * @notice Computes the index of the committee member that acts as proposer for a given slot * diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 084870f412d..6331b455212 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -23,6 +23,8 @@ import {PortalERC20} from "./portals/PortalERC20.sol"; import {TxsDecoderHelper} from "./decoders/helpers/TxsDecoderHelper.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; +// solhint-disable comprehensive-interface + /** * Blocks are generated using the `integration_l1_publisher.test.ts` tests. * Main use of these test is shorter cycles when updating the decoder contract. @@ -113,13 +115,7 @@ contract RollupTest is DecoderBase { _testBlock("mixed_block_1", false); - uint256 currentSlot = rollup.getCurrentSlot(); - (,, uint128 slot) = rollup.blocks(1); - uint256 prunableAt = uint256(slot) + rollup.TIMELINESS_PROVING_IN_SLOTS(); - - vm.expectRevert( - abi.encodeWithSelector(Errors.Rollup__NotReadyToPrune.selector, currentSlot, prunableAt) - ); + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); rollup.prune(); } diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 9a8e84d143b..44700b1e4f5 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -21,6 +21,8 @@ import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol"; import {IFeeJuicePortal} from "../../src/core/interfaces/IFeeJuicePortal.sol"; import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; +// solhint-disable comprehensive-interface + /** * We are using the same blocks as from Rollup.t.sol. * The tests in this file is testing the sequencer selection @@ -28,6 +30,12 @@ import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol"; contract SpartaTest is DecoderBase { using MessageHashUtils for bytes32; + struct StructToAvoidDeepStacks { + uint256 needed; + address proposer; + bool shouldRevert; + } + Registry internal registry; Inbox internal inbox; Outbox internal outbox; @@ -36,9 +44,10 @@ contract SpartaTest is DecoderBase { TxsDecoderHelper internal txsHelper; PortalERC20 internal portalERC20; - mapping(address validator => uint256 privateKey) internal privateKeys; - SignatureLib.Signature internal emptySignature; + mapping(address validator => uint256 privateKey) internal privateKeys; + mapping(address => bool) internal _seenValidators; + mapping(address => bool) internal _seenCommittee; /** * @notice Set up the contracts needed for the tests with time aligned to the provided block name @@ -78,9 +87,6 @@ contract SpartaTest is DecoderBase { _; } - mapping(address => bool) internal _seenValidators; - mapping(address => bool) internal _seenCommittee; - function testInitialCommitteMatch() public setup(4) { address[] memory validators = rollup.getValidators(); address[] memory committee = rollup.getCurrentEpochCommittee(); @@ -145,12 +151,6 @@ contract SpartaTest is DecoderBase { _testBlock("mixed_block_1", true, 2, false); } - struct StructToAvoidDeepStacks { - uint256 needed; - address proposer; - bool shouldRevert; - } - function _testBlock( string memory _name, bool _expectRevert, From 4cdeb3bf3e8c04ff6797fd494371727f642b7612 Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 17 Sep 2024 20:33:17 -0400 Subject: [PATCH 03/43] update getSlotAt check --- l1-contracts/src/core/sequencer_selection/Leonidas.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/src/core/sequencer_selection/Leonidas.sol b/l1-contracts/src/core/sequencer_selection/Leonidas.sol index 1b9484658c6..8a90052500f 100644 --- a/l1-contracts/src/core/sequencer_selection/Leonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/Leonidas.sol @@ -311,7 +311,7 @@ contract Leonidas is Ownable, ILeonidas { * @return The computed slot */ function getSlotAt(uint256 _ts) public view override(ILeonidas) returns (uint256) { - return (_ts - GENESIS_TIME) / SLOT_DURATION; + return _ts < GENESIS_TIME ? 0 : (_ts - GENESIS_TIME) / SLOT_DURATION; } /** From 9d09b1d1e4a586d418e29c0b081e88b67f3a631d Mon Sep 17 00:00:00 2001 From: Mitch Date: Wed, 18 Sep 2024 14:44:56 -0400 Subject: [PATCH 04/43] Update test to hit code path --- l1-contracts/test/Rollup.t.sol | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 6331b455212..5a32fc4b905 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -119,7 +119,7 @@ contract RollupTest is DecoderBase { rollup.prune(); } - function testPrune() public setUpFor("mixed_block_1") { + function testPruneDuringPropose() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false); assertEq(inbox.inProgress(), 3, "Invalid in progress"); @@ -129,7 +129,7 @@ contract RollupTest is DecoderBase { bytes32 inboxRoot2 = inbox.getRoot(2); (,, uint128 slot) = rollup.blocks(1); - uint256 prunableAt = uint256(slot) + rollup.TIMELINESS_PROVING_IN_SLOTS(); + uint256 prunableAt = uint256(slot) + Constants.AZTEC_EPOCH_DURATION * 2; uint256 timeOfPrune = rollup.getTimestampForSlot(prunableAt); vm.warp(timeOfPrune); @@ -148,16 +148,12 @@ contract RollupTest is DecoderBase { assertNotEq(rootMixed, bytes32(0), "Invalid root"); assertNotEq(minHeightMixed, 0, "Invalid min height"); - rollup.prune(); - assertEq(inbox.inProgress(), 3, "Invalid in progress"); - assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - // @note We alter what slot is specified in the empty block! // This means that we keep the `empty_block_1` mostly as is, but replace the slot number // and timestamp as if it was created at a different point in time. This allow us to insert it // as if it was the first block, even after we had originally inserted the mixed block. - // An example where this could happen would be if no-one could proof the mixed block. + // An example where this could happen would be if no-one could prove the mixed block. + // @note We prune the pending chain as part of the propose call. _testBlock("empty_block_1", false, prunableAt); assertEq(inbox.inProgress(), 3, "Invalid in progress"); From 6b1a7ba3d27b01bb841daf8cec51950fcddda264 Mon Sep 17 00:00:00 2001 From: just-mitch <68168980+just-mitch@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:19:18 -0400 Subject: [PATCH 05/43] feat: proofClaim in Rollup (#8636) Fix #8608 Add the proofClaim to the rollup. Update the `canPrune` logic to account for it. --- l1-contracts/src/core/Rollup.sol | 93 +++++- l1-contracts/src/core/interfaces/IRollup.sol | 13 + .../src/core/libraries/DataStructures.sol | 8 + l1-contracts/src/core/libraries/Errors.sol | 22 +- l1-contracts/test/Rollup.t.sol | 279 ++++++++++++++++++ 5 files changed, 403 insertions(+), 12 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 71a7f7f0f2b..7e8aec2f39b 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -53,6 +53,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup { // @todo #8018 uint256 public constant TIMELINESS_PROVING_IN_SLOTS = 100; + uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; + uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; + uint256 public immutable L1_BLOCK_AT_GENESIS; IRegistry public immutable REGISTRY; IInbox public immutable INBOX; @@ -63,6 +66,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { IVerifier public verifier; ChainTips public tips; + DataStructures.EpochProofClaim public proofClaim; // @todo Validate assumption: // Currently we assume that the archive root following a block is specific to the block @@ -177,6 +181,60 @@ contract Rollup is Leonidas, IRollup, ITestRollup { vkTreeRoot = _vkTreeRoot; } + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) + external + override(IRollup) + { + uint256 currentSlot = getCurrentSlot(); + address currentProposer = getCurrentProposer(); + uint256 epochToProve = getEpochToProve(); + + if (currentProposer != address(0) && currentProposer != msg.sender) { + revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); + } + + if (_quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); + } + + if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { + revert Errors.Rollup__NotInClaimPhase( + currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS + ); + } + + // if the epoch to prove is not the one that has been claimed, + // then whatever is in the proofClaim is stale + if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { + revert Errors.Rollup__ProofRightAlreadyClaimed(); + } + + if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + revert Errors.Rollup__InsufficientBondAmount( + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount + ); + } + + if (_quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); + } + + // We don't currently unstake, + // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. + // Blocked on submitting epoch proofs to this contract. + address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote); + + proofClaim = DataStructures.EpochProofClaim({ + epochToProve: epochToProve, + basisPointFee: _quote.basisPointFee, + bondAmount: _quote.bondAmount, + bondProvider: bondProvider, + proposerClaimant: msg.sender + }); + + emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot); + } + /** * @notice Publishes the body and propose the block * @dev `eth_log_handlers` rely on this function @@ -480,7 +538,26 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + /** + * @notice Get the epoch that should be proven + * + * @dev This is the epoch that should be proven. It does so by getting the epoch of the block + * following the last proven block. If there is no such block (i.e. the pending chain is + * the same as the proven chain), then revert. + * + * @return uint256 - The epoch to prove + */ + function getEpochToProve() public view override(IRollup) returns (uint256) { + if (tips.provenBlockNumber == tips.pendingBlockNumber) { + revert Errors.Rollup__NoEpochToProve(); + } else { + return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber); + } + } + function _prune() internal { + delete proofClaim; + uint256 pending = tips.pendingBlockNumber; // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. @@ -501,12 +578,20 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; - // TODO: #8608 adds a proof claim, which will allow us to prune the chain more aggressively. - // That is what will add a `CLAIM_DURATION` to the pruning logic. - if (currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION) { + // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. + // we prune the pending chain back to the end of epoch 1 if: + // - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2) + // - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted) + bool inClaimPhase = currentSlot + < startSlotOfPendingEpoch + Constants.AZTEC_EPOCH_DURATION + CLAIM_DURATION_IN_L2_SLOTS; + + bool claimExists = currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION + && proofClaim.epochToProve == oldestPendingEpoch && proofClaim.proposerClaimant != address(0); + + if (inClaimPhase || claimExists) { + // If we are in the claim phase, do not prune return false; } - return true; } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 4fe5e30dab2..464f4c93a96 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -18,9 +18,18 @@ interface IRollup { event L2BlockProposed(uint256 indexed blockNumber, bytes32 indexed archive); event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId); event PrunedPending(uint256 provenBlockNumber, uint256 pendingBlockNumber); + event ProofRightClaimed( + uint256 indexed epoch, + address indexed bondProvider, + address indexed proposer, + uint256 bondAmount, + uint256 currentSlot + ); function prune() external; + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external; + function propose( bytes calldata _header, bytes32 _archive, @@ -48,10 +57,13 @@ interface IRollup { DataStructures.ExecutionFlags memory _flags ) external view; + // solhint-disable-next-line func-name-mixedcase function INBOX() external view returns (IInbox); + // solhint-disable-next-line func-name-mixedcase function OUTBOX() external view returns (IOutbox); + // solhint-disable-next-line func-name-mixedcase function L1_BLOCK_AT_GENESIS() external view returns (uint256); function status(uint256 myHeaderBlockNumber) @@ -81,5 +93,6 @@ interface IRollup { function archiveAt(uint256 _blockNumber) external view returns (bytes32); function getProvenBlockNumber() external view returns (uint256); function getPendingBlockNumber() external view returns (uint256); + function getEpochToProve() external view returns (uint256); function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); } diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 2b6438c3702..dcfa1b75888 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -90,4 +90,12 @@ library DataStructures { address rollup; uint32 basisPointFee; } + + struct EpochProofClaim { + uint256 epochToProve; // the epoch that the bond provider is claiming to prove + uint256 basisPointFee; // the fee that the bond provider will receive as a percentage of the block rewards + uint256 bondAmount; // the amount of escrowed funds that the bond provider will stake. Must be at least PROOF_COMMITMENT_BOND_AMOUNT + address bondProvider; // the address that has deposited funds in the escrow contract + address proposerClaimant; // the address of the proposer that submitted the claim + } } diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index fab6b2bac33..b5086474992 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -44,23 +44,29 @@ library Errors { error Outbox__BlockNotProven(uint256 l2BlockNumber); // 0x0e194a6d // Rollup + error Rollup__InsufficientBondAmount(uint256 minimum, uint256 provided); // 0xa165f276 error Rollup__InvalidArchive(bytes32 expected, bytes32 actual); // 0xb682a40e - error Rollup__InvalidProposedArchive(bytes32 expected, bytes32 actual); // 0x32532e73 error Rollup__InvalidBlockNumber(uint256 expected, uint256 actual); // 0xe5edf847 - error Rollup__SlotValueTooLarge(uint256 slot); // 0x7234f4fe - error Rollup__SlotAlreadyInChain(uint256 lastSlot, uint256 proposedSlot); // 0x83510bd0 + error Rollup__InvalidChainId(uint256 expected, uint256 actual); // 0x37b5bc12 error Rollup__InvalidEpoch(uint256 expected, uint256 actual); // 0x3c6d65e6 - error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954 error Rollup__InvalidInHash(bytes32 expected, bytes32 actual); // 0xcd6f4233 error Rollup__InvalidProof(); // 0xa5b2ba17 - error Rollup__InvalidChainId(uint256 expected, uint256 actual); // 0x37b5bc12 - error Rollup__InvalidVersion(uint256 expected, uint256 actual); // 0x9ef30794 + error Rollup__InvalidProposedArchive(bytes32 expected, bytes32 actual); // 0x32532e73 error Rollup__InvalidTimestamp(uint256 expected, uint256 actual); // 0x3132e895 + error Rollup__InvalidVersion(uint256 expected, uint256 actual); // 0x9ef30794 + error Rollup__NoEpochToProve(); // 0xcbaa3951 + error Rollup__NonSequentialProving(); // 0x1e5be132 + error Rollup__NotClaimingCorrectEpoch(uint256 expected, uint256 actual); // 0xf0e0744d + error Rollup__NothingToPrune(); // 0x850defd3 + error Rollup__NotInClaimPhase(uint256 currentSlotInEpoch, uint256 claimDuration); // 0xe6969f11 + error Rollup__ProofRightAlreadyClaimed(); // 0x2cac5f0a + error Rollup__QuoteExpired(uint256 currentSlot, uint256 quoteSlot); // 0x20a001eb + error Rollup__SlotAlreadyInChain(uint256 lastSlot, uint256 proposedSlot); // 0x83510bd0 + error Rollup__SlotValueTooLarge(uint256 slot); // 0x7234f4fe error Rollup__TimestampInFuture(uint256 max, uint256 actual); // 0x89f30690 error Rollup__TimestampTooOld(); // 0x72ed9c81 + error Rollup__TryingToProveNonExistingBlock(); // 0x34ef4954 error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3 - error Rollup__NothingToPrune(); // 0x850defd3 - error Rollup__NonSequentialProving(); // 0x1e5be132 // Registry error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 5a32fc4b905..0a9eacf8bf4 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -14,6 +14,7 @@ import {Outbox} from "../src/core/messagebridge/Outbox.sol"; import {Errors} from "../src/core/libraries/Errors.sol"; import {Rollup} from "../src/core/Rollup.sol"; import {IFeeJuicePortal} from "../src/core/interfaces/IFeeJuicePortal.sol"; +import {IRollup} from "../src/core/interfaces/IRollup.sol"; import {FeeJuicePortal} from "../src/core/FeeJuicePortal.sol"; import {Leonidas} from "../src/core/sequencer_selection/Leonidas.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; @@ -78,6 +79,284 @@ contract RollupTest is DecoderBase { _; } + function warpToL2Slot(uint256 _slot) public { + vm.warp(rollup.getTimestampForSlot(_slot)); + } + + function testClaimWithNothingToProve() public setUpFor("mixed_block_1") { + assertEq(rollup.getCurrentSlot(), 0, "genesis slot should be zero"); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 1, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + // sanity check that proven/pending tip are at genesis + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); + rollup.claimEpochProofRight(quote); + + warpToL2Slot(1); + assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); + assertEq(rollup.getCurrentEpoch(), 0, "Invalid current epoch"); + + // empty slots do not move pending chain + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); + rollup.claimEpochProofRight(quote); + } + + function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 1, + validUntilSlot: 1, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + vm.expectRevert( + abi.encodeWithSelector(Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.epochToProve) + ); + rollup.claimEpochProofRight(quote); + } + + function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 1, + bondAmount: 0, + rollup: address(0), + basisPointFee: 0 + }); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.Rollup__InsufficientBondAmount.selector, + rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + quote.bondAmount + ) + ); + rollup.claimEpochProofRight(quote); + } + + function testClaimPastValidUntil() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 0, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(1); + + vm.expectRevert( + abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.validUntilSlot) + ); + rollup.claimEpochProofRight(quote); + } + + function testClaimSimple() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 1, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(1); + + vm.expectEmit(true, true, true, true); + emit IRollup.ProofRightClaimed( + quote.epochToProve, address(0), address(this), quote.bondAmount, 1 + ); + rollup.claimEpochProofRight(quote); + + ( + uint256 epochToProve, + uint256 basisPointFee, + uint256 bondAmount, + address bondProvider, + address proposerClaimant + ) = rollup.proofClaim(); + assertEq(epochToProve, quote.epochToProve, "Invalid epoch to prove"); + assertEq(basisPointFee, quote.basisPointFee, "Invalid basis point fee"); + assertEq(bondAmount, quote.bondAmount, "Invalid bond amount"); + // TODO #8573 + // This will be fixed with proper escrow + assertEq(bondProvider, address(0), "Invalid bond provider"); + assertEq(proposerClaimant, address(this), "Invalid proposer claimant"); + } + + function testClaimTwice() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 1, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(1); + + rollup.claimEpochProofRight(quote); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); + rollup.claimEpochProofRight(quote); + + warpToL2Slot(2); + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); + rollup.claimEpochProofRight(quote); + + // warp to epoch 1 + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); + assertEq(rollup.getCurrentEpoch(), 1, "Invalid current epoch"); + + // We should still be trying to prove epoch 0 in epoch 1 + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); + rollup.claimEpochProofRight(quote); + + // still nothing to prune + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); + rollup.prune(); + } + + function testClaimOutsideClaimPhase() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 1, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); + + vm.expectRevert( + abi.encodeWithSelector( + Errors.Rollup__NotInClaimPhase.selector, + rollup.CLAIM_DURATION_IN_L2_SLOTS(), + rollup.CLAIM_DURATION_IN_L2_SLOTS() + ) + ); + rollup.claimEpochProofRight(quote); + } + + function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); + + rollup.claimEpochProofRight(quote); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); + rollup.prune(); + } + + function testPruneWhenClaimExpires() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); + + rollup.claimEpochProofRight(quote); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); + + // We should still be trying to prove epoch 0 in epoch 2 + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); + rollup.claimEpochProofRight(quote); + + rollup.prune(); + + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); + rollup.claimEpochProofRight(quote); + } + + function testClaimAfterPrune() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false, 0); + + DataStructures.EpochProofQuote memory quote = DataStructures.EpochProofQuote({ + signature: SignatureLib.Signature({isEmpty: false, v: 27, r: bytes32(0), s: bytes32(0)}), + epochToProve: 0, + validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, + bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), + rollup: address(0), + basisPointFee: 0 + }); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); + + rollup.claimEpochProofRight(quote); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); + + rollup.prune(); + + _testBlock("mixed_block_1", false, Constants.AZTEC_EPOCH_DURATION * 2); + + vm.expectEmit(true, true, true, true); + emit IRollup.ProofRightClaimed( + quote.epochToProve, + address(0), + address(this), + quote.bondAmount, + Constants.AZTEC_EPOCH_DURATION * 2 + ); + rollup.claimEpochProofRight(quote); + } + + function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false); + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); + rollup.prune(); + + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); + rollup.prune(); + } + function testRevertProveTwice() public setUpFor("mixed_block_1") { DecoderBase.Data memory data = load("mixed_block_1").block; bytes memory header = data.header; From 09d306491ce8739237656866952755c8e4827915 Mon Sep 17 00:00:00 2001 From: Mitch Date: Thu, 19 Sep 2024 20:48:10 -0400 Subject: [PATCH 06/43] clean up escrow interface remove `TIMELINESS_PROVING_IN_SLOTS` update tests --- l1-contracts/src/core/Rollup.sol | 80 +++++++++---------- .../interfaces/IProofCommitmentEscrow.sol | 8 +- .../src/core/libraries/DataStructures.sol | 1 - .../src/mock/MockProofCommitmentEscrow.sol | 4 +- l1-contracts/test/Rollup.t.sol | 27 ++++--- .../end-to-end/src/e2e_synching.test.ts | 2 +- 6 files changed, 62 insertions(+), 60 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 7e8aec2f39b..d9d26527873 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -48,11 +48,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint128 slotNumber; } - // @note The number of slots within which a block must be proven - // This number is currently pulled out of thin air and should be replaced when we are not blind - // @todo #8018 - uint256 public constant TIMELINESS_PROVING_IN_SLOTS = 100; - + // See https://github.com/AztecProtocol/engineering-designs/blob/main/in-progress/8401-proof-timeliness/proof-timeliness.ipynb + // for justification of CLAIM_DURATION_IN_L2_SLOTS. uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; @@ -110,27 +107,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { setupEpoch(); } - function status(uint256 myHeaderBlockNumber) - external - view - override(IRollup) - returns ( - uint256 provenBlockNumber, - bytes32 provenArchive, - uint256 pendingBlockNumber, - bytes32 pendingArchive, - bytes32 archiveOfMyBlock - ) - { - return ( - tips.provenBlockNumber, - blocks[tips.provenBlockNumber].archive, - tips.pendingBlockNumber, - blocks[tips.pendingBlockNumber].archive, - archiveAt(myHeaderBlockNumber) - ); - } - /** * @notice Prune the pending chain up to the last proven block * @@ -222,7 +198,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. // Blocked on submitting epoch proofs to this contract. - address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote); + address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount); proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, @@ -452,6 +428,27 @@ contract Rollup is Leonidas, IRollup, ITestRollup { emit L2ProofVerified(header.globalVariables.blockNumber, _proverId); } + function status(uint256 myHeaderBlockNumber) + external + view + override(IRollup) + returns ( + uint256 provenBlockNumber, + bytes32 provenArchive, + uint256 pendingBlockNumber, + bytes32 pendingArchive, + bytes32 archiveOfMyBlock + ) + { + return ( + tips.provenBlockNumber, + blocks[tips.provenBlockNumber].archive, + tips.pendingBlockNumber, + blocks[tips.pendingBlockNumber].archive, + archiveAt(myHeaderBlockNumber) + ); + } + /** * @notice Check if msg.sender can propose at a given time * @@ -555,7 +552,22 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } } + /** + * @notice Get the archive root of a specific block + * + * @param _blockNumber - The block number to get the archive root of + * + * @return bytes32 - The archive root of the block + */ + function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { + if (_blockNumber <= tips.pendingBlockNumber) { + return blocks[_blockNumber].archive; + } + return bytes32(0); + } + function _prune() internal { + // TODO #8656 delete proofClaim; uint256 pending = tips.pendingBlockNumber; @@ -595,20 +607,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return true; } - /** - * @notice Get the archive root of a specific block - * - * @param _blockNumber - The block number to get the archive root of - * - * @return bytes32 - The archive root of the block - */ - function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { - if (_blockNumber <= tips.pendingBlockNumber) { - return blocks[_blockNumber].archive; - } - return bytes32(0); - } - /** * @notice Validates the header for submission * diff --git a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol index 8ef52a2e670..77b3fba206c 100644 --- a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol +++ b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol @@ -2,12 +2,14 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.18; -import {DataStructures} from "../libraries/DataStructures.sol"; +import {SignatureLib} from "../libraries/SignatureLib.sol"; interface IProofCommitmentEscrow { function deposit(uint256 _amount) external; function withdraw(uint256 _amount) external; // returns the address of the bond provider - function stakeBond(DataStructures.EpochProofQuote calldata _quote) external returns (address); - function unstakeBond(uint256 _amount) external; + function stakeBond(SignatureLib.Signature calldata _signature, uint256 _bondAmount) + external + returns (address); + function unstakeBond(uint256 _bondAmount) external; } diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index dcfa1b75888..f22eb10cd0d 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -87,7 +87,6 @@ library DataStructures { uint256 epochToProve; uint256 validUntilSlot; uint256 bondAmount; - address rollup; uint32 basisPointFee; } diff --git a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol index d7bc1d847e4..9f62f7b76c7 100644 --- a/l1-contracts/src/mock/MockProofCommitmentEscrow.sol +++ b/l1-contracts/src/mock/MockProofCommitmentEscrow.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.18; -import {DataStructures} from "../core/libraries/DataStructures.sol"; +import {SignatureLib} from "../core/libraries/SignatureLib.sol"; import {IProofCommitmentEscrow} from "../core/interfaces/IProofCommitmentEscrow.sol"; contract MockProofCommitmentEscrow is IProofCommitmentEscrow { @@ -18,7 +18,7 @@ contract MockProofCommitmentEscrow is IProofCommitmentEscrow { // do nothing } - function stakeBond(DataStructures.EpochProofQuote calldata) + function stakeBond(SignatureLib.Signature calldata, uint256) external pure override diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 0a9eacf8bf4..5c318419316 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -91,7 +91,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 1, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -116,7 +115,6 @@ contract RollupTest is DecoderBase { epochToProve: 1, validUntilSlot: 1, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -134,7 +132,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 1, bondAmount: 0, - rollup: address(0), basisPointFee: 0 }); @@ -156,7 +153,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 0, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -176,7 +172,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 1, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -212,7 +207,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 1, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -248,7 +242,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 1, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -272,7 +265,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -294,7 +286,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -322,7 +313,6 @@ contract RollupTest is DecoderBase { epochToProve: 0, validUntilSlot: 2 * Constants.AZTEC_EPOCH_DURATION, bondAmount: rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - rollup: address(0), basisPointFee: 0 }); @@ -398,7 +388,7 @@ contract RollupTest is DecoderBase { rollup.prune(); } - function testPruneDuringPropose() public setUpFor("mixed_block_1") { + function testPrune() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false); assertEq(inbox.inProgress(), 3, "Invalid in progress"); @@ -427,12 +417,16 @@ contract RollupTest is DecoderBase { assertNotEq(rootMixed, bytes32(0), "Invalid root"); assertNotEq(minHeightMixed, 0, "Invalid min height"); + rollup.prune(); + assertEq(inbox.inProgress(), 3, "Invalid in progress"); + assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); + // @note We alter what slot is specified in the empty block! // This means that we keep the `empty_block_1` mostly as is, but replace the slot number // and timestamp as if it was created at a different point in time. This allow us to insert it // as if it was the first block, even after we had originally inserted the mixed block. // An example where this could happen would be if no-one could prove the mixed block. - // @note We prune the pending chain as part of the propose call. _testBlock("empty_block_1", false, prunableAt); assertEq(inbox.inProgress(), 3, "Invalid in progress"); @@ -451,6 +445,15 @@ contract RollupTest is DecoderBase { assertNotEq(minHeightEmpty, minHeightMixed, "Invalid min height"); } + function testPruneDuringPropose() public setUpFor("mixed_block_1") { + _testBlock("mixed_block_1", false); + warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); + _testBlock("mixed_block_1", false, Constants.AZTEC_EPOCH_DURATION * 2); + + assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); + } + function testBlockFee() public setUpFor("mixed_block_1") { uint256 feeAmount = 2e18; diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 2cbb21530fa..a0f980bab34 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -451,7 +451,7 @@ describe('e2e_synching', () => { const pendingBlockNumber = await rollup.read.getPendingBlockNumber(); await rollup.write.setAssumeProvenThroughBlockNumber([pendingBlockNumber - BigInt(variant.blockCount) / 2n]); - const timeliness = await rollup.read.TIMELINESS_PROVING_IN_SLOTS(); + const timeliness = (await rollup.read.EPOCH_DURATION()) * 2n; const [, , slot] = await rollup.read.blocks([(await rollup.read.getProvenBlockNumber()) + 1n]); const timeJumpTo = await rollup.read.getTimestampForSlot([slot + timeliness]); From 1fa4fbafca4ef2eb66792e2f36cf68b3205146f6 Mon Sep 17 00:00:00 2001 From: Mitch Date: Mon, 23 Sep 2024 13:33:18 -0400 Subject: [PATCH 07/43] feat: basic epoch proof quote pool and interface on aztec node --- .../aztec-node/src/aztec-node/server.ts | 15 +++- .../src/aztec_node/rpc/aztec_node_client.ts | 8 +-- yarn-project/circuit-types/src/index.ts | 5 +- .../src/interfaces/aztec-node.ts | 71 ++++++++++--------- .../src/interfaces/tx-provider.ts | 7 ++ .../src/prover_coordination/index.ts | 2 + .../e2e_json_coordination.test.ts | 40 +++++++++++ yarn-project/p2p/src/client/index.ts | 12 +++- .../p2p/src/client/p2p_client.test.ts | 20 ++++-- yarn-project/p2p/src/client/p2p_client.ts | 48 ++++++++++--- .../epoch_proof_quote_pool.ts | 6 ++ .../p2p/src/epoch_proof_quote_pool/index.ts | 3 + .../memory_epoch_proof_quote_pool.test.ts | 21 ++++++ .../memory_epoch_proof_quote_pool.ts | 18 +++++ .../src/epoch_proof_quote_pool/test_utils.ts | 25 +++++++ yarn-project/p2p/src/index.ts | 7 +- .../reqresp/p2p_client.integration.test.ts | 8 +++ yarn-project/prover-node/src/prover-node.ts | 19 +++-- .../src/tx-provider/aztec-node-tx-provider.ts | 6 +- 19 files changed, 272 insertions(+), 69 deletions(-) create mode 100644 yarn-project/circuit-types/src/prover_coordination/index.ts create mode 100644 yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/index.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index cf655779674..87aa30d404e 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -3,6 +3,7 @@ import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover'; import { type AztecNode, type ClientProtocolCircuitVerifier, + type EpochProofQuote, type FromLogType, type GetUnencryptedLogsResponse, type L1ToL2MessageSource, @@ -58,6 +59,7 @@ import { DataTxValidator, DoubleSpendTxValidator, InMemoryAttestationPool, + MemoryEpochProofQuotePool, MetadataTxValidator, type P2P, TxProofValidator, @@ -120,6 +122,14 @@ export class AztecNodeService implements AztecNode { this.log.info(message); } + addEpochProofQuote(quote: EpochProofQuote): Promise { + return Promise.resolve(this.p2pClient.broadcastEpochProofQuote(quote)); + } + + getEpochProofQuotes(epoch: bigint): Promise { + return this.p2pClient.getEpochProofQuotes(epoch); + } + /** * initializes the Aztec Node, wait for component to sync. * @param config - The configuration to be used by the aztec node. @@ -153,6 +163,7 @@ export class AztecNodeService implements AztecNode { const p2pClient = await createP2PClient( config, new InMemoryAttestationPool(), + new MemoryEpochProofQuotePool(), archiver, proofVerifier, worldStateSynchronizer, @@ -228,8 +239,8 @@ export class AztecNodeService implements AztecNode { * Method to determine if the node is ready to accept transactions. * @returns - Flag indicating the readiness for tx submission. */ - public async isReady() { - return (await this.p2pClient.isReady()) ?? false; + public isReady() { + return Promise.resolve(this.p2pClient.isReady() ?? false); } /** diff --git a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts index 73c2da7cab0..e38f04f9f9e 100644 --- a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts +++ b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts @@ -31,19 +31,19 @@ export function createAztecNodeClient(url: string, fetch = defaultFetch): AztecN url, { AztecAddress, + Buffer32, EthAddress, + EventSelector, ExtendedUnencryptedL2Log, Fr, - EventSelector, FunctionSelector, Header, L2Block, - TxEffect, LogId, - TxHash, - Buffer32, PublicDataWitness, SiblingPath, + TxEffect, + TxHash, }, { EncryptedNoteL2BlockL2Logs, diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index 187539e9736..12c74f0c8f8 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -3,6 +3,7 @@ export * from './auth_witness.js'; export * from './aztec_node/rpc/index.js'; export * from './body.js'; export * from './function_call.js'; +export * from './global_variable_builder.js'; export * from './interfaces/index.js'; export * from './l2_block.js'; export * from './l2_block_downloader/index.js'; @@ -12,7 +13,9 @@ export * from './merkle_tree_id.js'; export * from './messaging/index.js'; export * from './mocks.js'; export * from './notes/index.js'; +export * from './p2p/index.js'; export * from './packed_values.js'; +export * from './prover_coordination/index.js'; export * from './public_data_witness.js'; export * from './public_data_write.js'; export * from './public_execution_request.js'; @@ -21,5 +24,3 @@ export * from './simulation_error.js'; export * from './tx/index.js'; export * from './tx_effect.js'; export * from './tx_execution_request.js'; -export * from './p2p/index.js'; -export * from './global_variable_builder.js'; diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index 16c64a1ac42..733982bb432 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -1,38 +1,33 @@ -import { - type ARCHIVE_HEIGHT, - type Header, - type L1_TO_L2_MSG_TREE_HEIGHT, - type NOTE_HASH_TREE_HEIGHT, - type NULLIFIER_TREE_HEIGHT, - type PUBLIC_DATA_TREE_HEIGHT, +import type { + ARCHIVE_HEIGHT, + Header, + L1_TO_L2_MSG_TREE_HEIGHT, + NOTE_HASH_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/circuits.js'; -import { type L1ContractAddresses } from '@aztec/ethereum'; -import { type ContractArtifact } from '@aztec/foundation/abi'; -import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { type Fr } from '@aztec/foundation/fields'; -import { - type ContractClassPublic, - type ContractInstanceWithAddress, - type ProtocolContractAddresses, +import type { L1ContractAddresses } from '@aztec/ethereum'; +import type { ContractArtifact } from '@aztec/foundation/abi'; +import type { AztecAddress } from '@aztec/foundation/aztec-address'; +import type { Fr } from '@aztec/foundation/fields'; +import type { + ContractClassPublic, + ContractInstanceWithAddress, + ProtocolContractAddresses, } from '@aztec/types/contracts'; -import { type L2Block } from '../l2_block.js'; -import { - type FromLogType, - type GetUnencryptedLogsResponse, - type L2BlockL2Logs, - type LogFilter, - type LogType, -} from '../logs/index.js'; -import { type MerkleTreeId } from '../merkle_tree_id.js'; -import { type PublicDataWitness } from '../public_data_witness.js'; -import { type SiblingPath } from '../sibling_path/index.js'; -import { type PublicSimulationOutput, type Tx, type TxHash, type TxReceipt } from '../tx/index.js'; -import { type TxEffect } from '../tx_effect.js'; -import { type SequencerConfig } from './configs.js'; -import { type L2BlockNumber } from './l2_block_number.js'; -import { type NullifierMembershipWitness } from './nullifier_tree.js'; -import { type ProverConfig } from './prover-client.js'; +import type { L2Block } from '../l2_block.js'; +import type { FromLogType, GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js'; +import type { MerkleTreeId } from '../merkle_tree_id.js'; +import type { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; +import type { PublicDataWitness } from '../public_data_witness.js'; +import type { SiblingPath } from '../sibling_path/index.js'; +import type { PublicSimulationOutput, Tx, TxHash, TxReceipt } from '../tx/index.js'; +import type { TxEffect } from '../tx_effect.js'; +import type { SequencerConfig } from './configs.js'; +import type { L2BlockNumber } from './l2_block_number.js'; +import type { NullifierMembershipWitness } from './nullifier_tree.js'; +import type { ProverConfig } from './prover-client.js'; /** * The aztec node. @@ -356,4 +351,16 @@ export interface AztecNode { * Returns the ENR of this node for peer discovery, if available. */ getEncodedEnr(): Promise; + + /** + * Receives a quote for an epoch proof and stores it in its EpochProofQuotePool + * @param quote - The quote to store + */ + addEpochProofQuote(quote: EpochProofQuote): Promise; + + /** + * Returns the received quotes for a given epoch + * @param epoch - The epoch for which to get the quotes + */ + getEpochProofQuotes(epoch: bigint): Promise; } diff --git a/yarn-project/circuit-types/src/interfaces/tx-provider.ts b/yarn-project/circuit-types/src/interfaces/tx-provider.ts index 872f9ce0282..60b7f59e9b9 100644 --- a/yarn-project/circuit-types/src/interfaces/tx-provider.ts +++ b/yarn-project/circuit-types/src/interfaces/tx-provider.ts @@ -1,3 +1,4 @@ +import { type EpochProofQuote } from '../prover_coordination/index.js'; import { type Tx } from '../tx/tx.js'; import { type TxHash } from '../tx/tx_hash.js'; @@ -9,4 +10,10 @@ export interface TxProvider { * @returns The transaction, if found, 'undefined' otherwise. */ getTxByHash(txHash: TxHash): Promise; + + /** + * Receives a quote for an epoch proof and stores it in its EpochProofQuotePool + * @param quote - The quote to store + */ + addEpochProofQuote(quote: EpochProofQuote): Promise; } diff --git a/yarn-project/circuit-types/src/prover_coordination/index.ts b/yarn-project/circuit-types/src/prover_coordination/index.ts new file mode 100644 index 00000000000..331978ec556 --- /dev/null +++ b/yarn-project/circuit-types/src/prover_coordination/index.ts @@ -0,0 +1,2 @@ +export * from './epoch_proof_quote.js'; +export * from './epoch_proof_quote_payload.js'; diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts new file mode 100644 index 00000000000..89172315e9a --- /dev/null +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -0,0 +1,40 @@ +import { type DebugLogger, createDebugLogger } from '@aztec/aztec.js'; +import { makeRandomEpochProofQuote } from '@aztec/p2p'; + +import { beforeAll } from '@jest/globals'; + +import { + type ISnapshotManager, + type SubsystemsContext, + addAccounts, + createSnapshotManager, +} from '../fixtures/snapshot_manager.js'; + +// Tests simple block building with a sequencer that does not upload proofs to L1, +// and then follows with a prover node run (with real proofs disabled, but +// still simulating all circuits via a prover-client), in order to test +// the coordination through L1 between the sequencer and the prover node. +describe('e2e_prover_node', () => { + let ctx: SubsystemsContext; + + let logger: DebugLogger; + let snapshotManager: ISnapshotManager; + + beforeAll(async () => { + logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); + snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); + + await snapshotManager.snapshot('setup', addAccounts(2, logger)); + + ctx = await snapshotManager.setup(); + }); + + it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { + const { quote } = makeRandomEpochProofQuote(); + + await ctx.proverNode.sendEpochProofQuote(quote); + const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); + expect(receivedQuotes.length).toBe(1); + expect(receivedQuotes[0]).toEqual(quote); + }); +}); diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index e36054fd7fd..f07c0fe8ed6 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -8,6 +8,7 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; import { P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; +import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; @@ -19,6 +20,7 @@ export * from './p2p_client.js'; export const createP2PClient = async ( _config: P2PConfig & DataStoreConfig, attestationsPool: AttestationPool, + epochProofQuotePool: EpochProofQuotePool, l2BlockSource: L2BlockSource, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, @@ -52,7 +54,15 @@ export const createP2PClient = async ( } else { p2pService = new DummyP2PService(); } - return new P2PClient(store, l2BlockSource, txPool, attestationsPool, p2pService, config.keepProvenTxsInPoolFor); + return new P2PClient( + store, + l2BlockSource, + txPool, + attestationsPool, + epochProofQuotePool, + p2pService, + config.keepProvenTxsInPoolFor, + ); }; async function configureP2PClientAddresses(_config: P2PConfig & DataStoreConfig): Promise { diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 196a0ee45b7..98164007f05 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -6,7 +6,7 @@ import { openTmpStore } from '@aztec/kv-store/utils'; import { expect, jest } from '@jest/globals'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; -import { type P2PService } from '../index.js'; +import { type EpochProofQuotePool, type P2PService } from '../index.js'; import { type TxPool } from '../tx_pool/index.js'; import { MockBlockSource } from './mocks.js'; import { P2PClient } from './p2p_client.js'; @@ -21,6 +21,7 @@ type Mockify = { describe('In-Memory P2P Client', () => { let txPool: Mockify; let attestationPool: Mockify; + let epochProofQuotePool: Mockify; let blockSource: MockBlockSource; let p2pService: Mockify; let kvStore: AztecKVStore; @@ -55,10 +56,15 @@ describe('In-Memory P2P Client', () => { getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }; + epochProofQuotePool = { + addQuote: jest.fn(), + getQuotes: jest.fn().mockReturnValue([]), + }; + blockSource = new MockBlockSource(); kvStore = openTmpStore(); - client = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 0); + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); }); const advanceToProvenBlock = async (getProvenBlockNumber: number) => { @@ -72,13 +78,13 @@ describe('In-Memory P2P Client', () => { }; it('can start & stop', async () => { - expect(await client.isReady()).toEqual(false); + expect(client.isReady()).toEqual(false); await client.start(); - expect(await client.isReady()).toEqual(true); + expect(client.isReady()).toEqual(true); await client.stop(); - expect(await client.isReady()).toEqual(false); + expect(client.isReady()).toEqual(false); }); it('adds txs to pool', async () => { @@ -121,7 +127,7 @@ describe('In-Memory P2P Client', () => { await client.start(); await client.stop(); - const client2 = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 0); + const client2 = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); expect(client2.getSyncedLatestBlockNum()).toEqual(client.getSyncedLatestBlockNum()); }); @@ -136,7 +142,7 @@ describe('In-Memory P2P Client', () => { }); it('deletes txs after waiting the set number of blocks', async () => { - client = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 10); + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 10); blockSource.setProvenBlockNumber(0); await client.start(); expect(txPool.deleteTxs).not.toHaveBeenCalled(); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 63c5b453f0a..6c6c1f6bdb5 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -1,6 +1,7 @@ import { type BlockAttestation, type BlockProposal, + type EpochProofQuote, type L2Block, L2BlockDownloader, type L2BlockSource, @@ -15,6 +16,7 @@ import { type ENR } from '@chainsafe/enr'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; import { getP2PConfigEnvVars } from '../config.js'; +import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { TX_REQ_PROTOCOL } from '../service/reqresp/interface.js'; import type { P2PService } from '../service/service.js'; import { type TxPool } from '../tx_pool/index.js'; @@ -62,6 +64,21 @@ export interface P2P { */ getAttestationsForSlot(slot: bigint): Promise; + /** + * Queries the EpochProofQuote pool for quotes for the given epoch + * + * @param epoch - the epoch to query + * @returns EpochProofQuotes + */ + getEpochProofQuotes(epoch: bigint): Promise; + + /** + * Broadcasts an EpochProofQuote to other peers. + * + * @param quote - the quote to broadcast + */ + broadcastEpochProofQuote(quote: EpochProofQuote): void; + /** * Registers a callback from the validator client that determines how to behave when * foreign block proposals are received @@ -134,7 +151,7 @@ export interface P2P { * Indicates if the p2p client is ready for transaction submission. * @returns A boolean flag indicating readiness. */ - isReady(): Promise; + isReady(): boolean; /** * Returns the current status of the p2p client. @@ -186,6 +203,7 @@ export class P2PClient implements P2P { private l2BlockSource: L2BlockSource, private txPool: TxPool, private attestationPool: AttestationPool, + private epochProofQuotePool: EpochProofQuotePool, private p2pService: P2PService, private keepProvenTxsFor: number, private log = createDebugLogger('aztec:p2p'), @@ -202,6 +220,22 @@ export class P2PClient implements P2P { this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block'); } + #assertIsReady() { + if (!this.isReady()) { + throw new Error('P2P client not ready'); + } + } + + getEpochProofQuotes(epoch: bigint): Promise { + return Promise.resolve(this.epochProofQuotePool.getQuotes(epoch)); + } + + broadcastEpochProofQuote(quote: EpochProofQuote): void { + this.#assertIsReady(); + this.epochProofQuotePool.addQuote(quote); + return this.p2pService.propagate(quote); + } + /** * Starts the P2P client. * @returns An empty promise signalling the synching process. @@ -363,10 +397,7 @@ export class P2PClient implements P2P { * @returns Empty promise. **/ public async sendTx(tx: Tx): Promise { - const ready = await this.isReady(); - if (!ready) { - throw new Error('P2P client not ready'); - } + this.#assertIsReady(); await this.txPool.addTxs([tx]); this.p2pService.propagate(tx); } @@ -391,10 +422,7 @@ export class P2PClient implements P2P { * @returns Empty promise. **/ public async deleteTxs(txHashes: TxHash[]): Promise { - const ready = await this.isReady(); - if (!ready) { - throw new Error('P2P client not ready'); - } + this.#assertIsReady(); await this.txPool.deleteTxs(txHashes); } @@ -403,7 +431,7 @@ export class P2PClient implements P2P { * @returns True if the P2P client is ready to receive txs. */ public isReady() { - return Promise.resolve(this.currentState === P2PClientState.RUNNING); + return this.currentState === P2PClientState.RUNNING; } /** diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts new file mode 100644 index 00000000000..6d9067c76d2 --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts @@ -0,0 +1,6 @@ +import { type EpochProofQuote } from '@aztec/circuit-types'; + +export interface EpochProofQuotePool { + addQuote(quote: EpochProofQuote): void; + getQuotes(epoch: bigint): EpochProofQuote[]; +} diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts new file mode 100644 index 00000000000..8073ff1866f --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts @@ -0,0 +1,3 @@ +export * from './epoch_proof_quote_pool.js'; +export * from './memory_epoch_proof_quote_pool.js'; +export * from './test_utils.js'; diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts new file mode 100644 index 00000000000..35c8611006f --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts @@ -0,0 +1,21 @@ +import { MemoryEpochProofQuotePool } from './memory_epoch_proof_quote_pool.js'; +import { makeRandomEpochProofQuote } from './test_utils.js'; + +describe('MemoryEpochProofQuotePool', () => { + let pool: MemoryEpochProofQuotePool; + + beforeEach(() => { + pool = new MemoryEpochProofQuotePool(); + }); + + it('should add/get quotes to/from pool', () => { + const { quote } = makeRandomEpochProofQuote(); + + pool.addQuote(quote); + + const quotes = pool.getQuotes(quote.payload.epochToProve); + + expect(quotes).toHaveLength(1); + expect(quotes[0]).toEqual(quote); + }); +}); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts new file mode 100644 index 00000000000..16b178c92fe --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts @@ -0,0 +1,18 @@ +import { type EpochProofQuote } from '@aztec/circuit-types'; + +export class MemoryEpochProofQuotePool { + private quotes: Map; + constructor() { + this.quotes = new Map(); + } + addQuote(quote: EpochProofQuote) { + const epoch = quote.payload.epochToProve; + if (!this.quotes.has(epoch)) { + this.quotes.set(epoch, []); + } + this.quotes.get(epoch)!.push(quote); + } + getQuotes(epoch: bigint): EpochProofQuote[] { + return this.quotes.get(epoch) || []; + } +} diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts new file mode 100644 index 00000000000..a12e10d85fa --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts @@ -0,0 +1,25 @@ +import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types'; +import { EthAddress } from '@aztec/circuits.js'; +import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto'; + +export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { + return EpochProofQuotePayload.fromFields({ + basisPointFee: randomInt(10000), + bondAmount: 1000000000000000000n, + epochToProve: randomBigInt(1000000n), + rollupAddress: EthAddress.random(), + validUntilSlot: randomBigInt(1000000n), + }); +} + +export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): { + quote: EpochProofQuote; + signer: Secp256k1Signer; +} { + const signer = Secp256k1Signer.random(); + + return { + quote: EpochProofQuote.new(payload ?? makeRandomEpochProofQuotePayload(), signer), + signer, + }; +} diff --git a/yarn-project/p2p/src/index.ts b/yarn-project/p2p/src/index.ts index e69651a7904..4a5c64fda7e 100644 --- a/yarn-project/p2p/src/index.ts +++ b/yarn-project/p2p/src/index.ts @@ -1,7 +1,8 @@ +export * from './attestation_pool/index.js'; +export * from './bootstrap/bootstrap.js'; export * from './client/index.js'; export * from './config.js'; -export * from './tx_pool/index.js'; -export * from './attestation_pool/index.js'; +export * from './epoch_proof_quote_pool/index.js'; export * from './service/index.js'; -export * from './bootstrap/bootstrap.js'; +export * from './tx_pool/index.js'; export * from './tx_validator/index.js'; diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts index 6364c497b86..ffd539f5e80 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts @@ -17,6 +17,7 @@ import { createP2PClient } from '../../client/index.js'; import { MockBlockSource } from '../../client/mocks.js'; import { type P2PClient } from '../../client/p2p_client.js'; import { type P2PConfig, getP2PDefaultConfig } from '../../config.js'; +import { type EpochProofQuotePool } from '../../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { AlwaysFalseCircuitVerifier, AlwaysTrueCircuitVerifier } from '../../mocks/index.js'; import { type TxPool } from '../../tx_pool/index.js'; import { convertToMultiaddr } from '../../util.js'; @@ -47,6 +48,7 @@ const NUMBER_OF_PEERS = 2; describe('Req Resp p2p client integration', () => { let txPool: Mockify; let attestationPool: Mockify; + let epochProofQuotePool: Mockify; let blockSource: MockBlockSource; let kvStore: AztecKVStore; let worldStateSynchronizer: WorldStateSynchronizer; @@ -134,6 +136,11 @@ describe('Req Resp p2p client integration', () => { getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }; + epochProofQuotePool = { + addQuote: jest.fn(), + getQuotes: jest.fn().mockReturnValue([]), + }; + blockSource = new MockBlockSource(); proofVerifier = alwaysTrueVerifier ? new AlwaysTrueCircuitVerifier() : new AlwaysFalseCircuitVerifier(); kvStore = openTmpStore(); @@ -144,6 +151,7 @@ describe('Req Resp p2p client integration', () => { const client = await createP2PClient( config, attestationPool as unknown as AttestationPool, + epochProofQuotePool as unknown as EpochProofQuotePool, blockSource, proofVerifier, worldStateSynchronizer, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 02b3a1b4448..42c30341512 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -1,10 +1,11 @@ -import { - type L1ToL2MessageSource, - type L2BlockSource, - type MerkleTreeOperations, - type ProverClient, - type TxProvider, - type WorldStateSynchronizer, +import type { + EpochProofQuote, + L1ToL2MessageSource, + L2BlockSource, + MerkleTreeOperations, + ProverClient, + TxProvider, + WorldStateSynchronizer, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; @@ -125,6 +126,10 @@ export class ProverNode { } } + public sendEpochProofQuote(quote: EpochProofQuote): Promise { + return this.txProvider.addEpochProofQuote(quote); + } + /** * Creates a proof for a block range. Returns once the proof has been submitted to L1. */ diff --git a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts index 90797602453..8e5dc75db18 100644 --- a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts +++ b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts @@ -1,4 +1,4 @@ -import { type AztecNode, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; +import { type AztecNode, type EpochProofQuote, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; /** Implements TxProvider by querying an Aztec node for the txs. */ export class AztecNodeTxProvider implements TxProvider { @@ -7,4 +7,8 @@ export class AztecNodeTxProvider implements TxProvider { getTxByHash(txHash: TxHash): Promise { return this.node.getTxByHash(txHash); } + + addEpochProofQuote(quote: EpochProofQuote): Promise { + return this.node.addEpochProofQuote(quote); + } } From ff68ae8a5146ab7f2baf1424a7e894f16a338a43 Mon Sep 17 00:00:00 2001 From: Mitch Date: Mon, 23 Sep 2024 15:07:04 -0400 Subject: [PATCH 08/43] rename TxProvider to ProverCoordination --- .../aztec-node/src/aztec-node/server.ts | 12 +- .../aztec/src/cli/cmds/start_prover_node.ts | 2 +- .../circuit-types/src/interfaces/index.ts | 14 +- ...{tx-provider.ts => prover-coordination.ts} | 4 +- .../src/e2e_prover/e2e_prover_test.ts | 2 +- .../end-to-end/src/e2e_prover_node.test.ts | 2 +- .../src/fixtures/snapshot_manager.ts | 2 +- yarn-project/foundation/src/config/env_var.ts | 214 +++++++++--------- yarn-project/p2p/src/client/index.ts | 13 +- .../reqresp/p2p_client.integration.test.ts | 13 +- yarn-project/prover-client/package.json | 2 +- yarn-project/prover-node/src/config.ts | 10 +- yarn-project/prover-node/src/factory.ts | 8 +- .../prover-node/src/job/block-proving-job.ts | 6 +- .../aztec-node-prover-coordination.ts} | 6 +- .../src/prover-coordination/config.ts | 17 ++ .../src/prover-coordination/factory.ts | 13 ++ .../index.ts | 4 +- .../prover-node/src/prover-node.test.ts | 6 +- yarn-project/prover-node/src/prover-node.ts | 8 +- .../prover-node/src/tx-provider/config.ts | 17 -- .../prover-node/src/tx-provider/factory.ts | 13 -- 22 files changed, 191 insertions(+), 197 deletions(-) rename yarn-project/circuit-types/src/interfaces/{tx-provider.ts => prover-coordination.ts} (82%) rename yarn-project/prover-node/src/{tx-provider/aztec-node-tx-provider.ts => prover-coordination/aztec-node-prover-coordination.ts} (52%) create mode 100644 yarn-project/prover-node/src/prover-coordination/config.ts create mode 100644 yarn-project/prover-node/src/prover-coordination/factory.ts rename yarn-project/prover-node/src/{tx-provider => prover-coordination}/index.ts (52%) delete mode 100644 yarn-project/prover-node/src/tx-provider/config.ts delete mode 100644 yarn-project/prover-node/src/tx-provider/factory.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 87aa30d404e..e6906af9b91 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -58,8 +58,6 @@ import { AggregateTxValidator, DataTxValidator, DoubleSpendTxValidator, - InMemoryAttestationPool, - MemoryEpochProofQuotePool, MetadataTxValidator, type P2P, TxProofValidator, @@ -160,15 +158,7 @@ export class AztecNodeService implements AztecNode { const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(); // create the tx pool and the p2p client, which will need the l2 block source - const p2pClient = await createP2PClient( - config, - new InMemoryAttestationPool(), - new MemoryEpochProofQuotePool(), - archiver, - proofVerifier, - worldStateSynchronizer, - telemetry, - ); + const p2pClient = await createP2PClient(config, archiver, proofVerifier, worldStateSynchronizer, telemetry); // start both and wait for them to sync from the block source await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]); diff --git a/yarn-project/aztec/src/cli/cmds/start_prover_node.ts b/yarn-project/aztec/src/cli/cmds/start_prover_node.ts index e1e95a9a1f8..ca054a01ec4 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover_node.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover_node.ts @@ -63,7 +63,7 @@ export const startProverNode = async ( // Load l1 contract addresses from aztec node if not set. const isRollupAddressSet = proverConfig.l1Contracts?.rollupAddress && !proverConfig.l1Contracts.rollupAddress.isZero(); - const nodeUrl = proverConfig.nodeUrl ?? proverConfig.txProviderNodeUrl; + const nodeUrl = proverConfig.nodeUrl ?? proverConfig.proverCoordinationNodeUrl; if (nodeUrl && !isRollupAddressSet) { userLog(`Loading L1 contract addresses from aztec node at ${nodeUrl}`); proverConfig.l1Contracts = await createAztecNodeClient(nodeUrl).getL1ContractAddresses(); diff --git a/yarn-project/circuit-types/src/interfaces/index.ts b/yarn-project/circuit-types/src/interfaces/index.ts index 1ef99ee52db..63a79b5a4be 100644 --- a/yarn-project/circuit-types/src/interfaces/index.ts +++ b/yarn-project/circuit-types/src/interfaces/index.ts @@ -1,14 +1,14 @@ export * from './aztec-node.js'; -export * from './l2_block_number.js'; -export * from './pxe.js'; -export * from './sync-status.js'; +export * from './block-prover.js'; export * from './configs.js'; +export * from './l2_block_number.js'; +export * from './merkle_tree_operations.js'; export * from './nullifier_tree.js'; +export * from './private_kernel_prover.js'; export * from './prover-client.js'; +export * from './prover-coordination.js'; export * from './proving-job.js'; -export * from './block-prover.js'; +export * from './pxe.js'; export * from './server_circuit_prover.js'; -export * from './private_kernel_prover.js'; -export * from './tx-provider.js'; -export * from './merkle_tree_operations.js'; +export * from './sync-status.js'; export * from './world_state.js'; diff --git a/yarn-project/circuit-types/src/interfaces/tx-provider.ts b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts similarity index 82% rename from yarn-project/circuit-types/src/interfaces/tx-provider.ts rename to yarn-project/circuit-types/src/interfaces/prover-coordination.ts index 60b7f59e9b9..01918a34d39 100644 --- a/yarn-project/circuit-types/src/interfaces/tx-provider.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts @@ -2,8 +2,8 @@ import { type EpochProofQuote } from '../prover_coordination/index.js'; import { type Tx } from '../tx/tx.js'; import { type TxHash } from '../tx/tx_hash.js'; -/** Provider for transaction objects given their hash. */ -export interface TxProvider { +/** Provides basic operations for ProverNodes to interact with other nodes in the network. */ +export interface ProverCoordination { /** * Returns a transaction given its hash if available. * @param txHash - The hash of the transaction, used as an ID. 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 fe3b7dac796..db1d570ba87 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 @@ -236,7 +236,7 @@ export class FullProverTest { this.logger.verbose('Starting fully proven prover node'); const proverConfig: ProverNodeConfig = { ...this.context.aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId: new Fr(81), realProofs: true, diff --git a/yarn-project/end-to-end/src/e2e_prover_node.test.ts b/yarn-project/end-to-end/src/e2e_prover_node.test.ts index efab32e13fd..d2cff793446 100644 --- a/yarn-project/end-to-end/src/e2e_prover_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover_node.test.ts @@ -115,7 +115,7 @@ describe('e2e_prover_node', () => { // snapshot manager does not include events nor txs, so a new archiver would not "see" old blocks. const proverConfig: ProverNodeConfig = { ...ctx.aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId, proverNodeMaxPendingJobs: 100, 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 bb1af6a9da5..a758372ea7c 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -244,7 +244,7 @@ export async function createAndSyncProverNode( // Prover node config is for simulated proofs const proverConfig: ProverNodeConfig = { ...aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId: new Fr(42), realProofs: false, diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 5183e2847ed..efbf60a5d4e 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -1,132 +1,132 @@ export type EnvVar = - | 'AZTEC_PORT' + | 'ACVM_BINARY_PATH' + | 'ACVM_WORKING_DIRECTORY' + | 'API_KEY' + | 'API_PREFIX' + | 'ARCHIVER_MAX_LOGS' + | 'ARCHIVER_POLLING_INTERVAL_MS' + | 'ARCHIVER_URL' + | 'ARCHIVER_VIEM_POLLING_INTERVAL_MS' | 'ASSUME_PROVEN_THROUGH_BLOCK_NUMBER' - | 'TEST_ACCOUNTS' + | 'AZTEC_NODE_URL' + | 'AZTEC_PORT' + | 'BB_BINARY_PATH' + | 'BB_SKIP_CLEANUP' + | 'BB_WORKING_DIRECTORY' + | 'BOOTSTRAP_NODES' + | 'BOT_DA_GAS_LIMIT' + | 'BOT_FEE_PAYMENT_METHOD' + | 'BOT_FLUSH_SETUP_TRANSACTIONS' + | 'BOT_FOLLOW_CHAIN' + | 'BOT_L2_GAS_LIMIT' + | 'BOT_MAX_PENDING_TXS' + | 'BOT_NO_START' + | 'BOT_NO_WAIT_FOR_TRANSFERS' + | 'BOT_PRIVATE_KEY' + | 'BOT_PRIVATE_TRANSFERS_PER_TX' + | 'BOT_PUBLIC_TRANSFERS_PER_TX' + | 'BOT_PXE_URL' + | 'BOT_RECIPIENT_ENCRYPTION_SECRET' + | 'BOT_SKIP_PUBLIC_SIMULATION' + | 'BOT_TOKEN_CONTRACT' + | 'BOT_TOKEN_SALT' + | 'BOT_TX_INTERVAL_SECONDS' + | 'BOT_TX_MINED_WAIT_SECONDS' + | 'COINBASE' + | 'DATA_DIRECTORY' + | 'DEBUG' + | 'DEPLOY_AZTEC_CONTRACTS_SALT' + | 'DEPLOY_AZTEC_CONTRACTS' | 'ENABLE_GAS' - | 'API_PREFIX' + | 'ENFORCE_FEES' | 'ETHEREUM_HOST' - | 'L1_CHAIN_ID' - | 'MNEMONIC' - | 'ROLLUP_CONTRACT_ADDRESS' - | 'REGISTRY_CONTRACT_ADDRESS' - | 'INBOX_CONTRACT_ADDRESS' - | 'OUTBOX_CONTRACT_ADDRESS' | 'FEE_JUICE_CONTRACT_ADDRESS' | 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS' - | 'ARCHIVER_URL' - | 'DEPLOY_AZTEC_CONTRACTS' - | 'DEPLOY_AZTEC_CONTRACTS_SALT' + | 'FEE_RECIPIENT' + | 'INBOX_CONTRACT_ADDRESS' + | 'L1_CHAIN_ID' | 'L1_PRIVATE_KEY' | 'L2_QUEUE_SIZE' - | 'WS_BLOCK_CHECK_INTERVAL_MS' - | 'P2P_ENABLED' + | 'LOG_JSON' + | 'LOG_LEVEL' + | 'MNEMONIC' + | 'NETWORK_NAME' + | 'NETWORK' + | 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' + | 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' + | 'OTEL_SERVICE_NAME' + | 'OUTBOX_CONTRACT_ADDRESS' | 'P2P_BLOCK_CHECK_INTERVAL_MS' - | 'P2P_PEER_CHECK_INTERVAL_MS' - | 'P2P_L2_QUEUE_SIZE' - | 'TCP_LISTEN_ADDR' - | 'UDP_LISTEN_ADDR' - | 'P2P_TCP_ANNOUNCE_ADDR' - | 'P2P_UDP_ANNOUNCE_ADDR' - | 'PEER_ID_PRIVATE_KEY' - | 'BOOTSTRAP_NODES' - | 'P2P_TX_PROTOCOL' - | 'P2P_MIN_PEERS' - | 'P2P_MAX_PEERS' - | 'DATA_DIRECTORY' - | 'TX_GOSSIP_VERSION' - | 'P2P_QUERY_FOR_IP' - | 'P2P_TX_POOL_KEEP_PROVEN_FOR' - | 'P2P_GOSSIPSUB_INTERVAL_MS' + | 'P2P_ENABLED' | 'P2P_GOSSIPSUB_D' - | 'P2P_GOSSIPSUB_DLO' | 'P2P_GOSSIPSUB_DHI' - | 'P2P_GOSSIPSUB_MCACHE_LENGTH' + | 'P2P_GOSSIPSUB_DLO' + | 'P2P_GOSSIPSUB_INTERVAL_MS' | 'P2P_GOSSIPSUB_MCACHE_GOSSIP' - | 'P2P_SEVERE_PEER_PENALTY_BLOCK_LENGTH' - | 'P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS' - | 'P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS' - | 'P2P_GOSSIPSUB_TX_TOPIC_WEIGHT' - | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT' + | 'P2P_GOSSIPSUB_MCACHE_LENGTH' | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY' + | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT' + | 'P2P_GOSSIPSUB_TX_TOPIC_WEIGHT' + | 'P2P_L2_QUEUE_SIZE' + | 'P2P_MAX_PEERS' + | 'P2P_MIN_PEERS' + | 'P2P_PEER_CHECK_INTERVAL_MS' | 'P2P_PEER_PENALTY_VALUES' - | 'TELEMETRY' - | 'OTEL_SERVICE_NAME' - | 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' - | 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - | 'NETWORK_NAME' - | 'NETWORK' - | 'API_KEY' - | 'AZTEC_NODE_URL' - | 'ARCHIVER_POLLING_INTERVAL_MS' - | 'ARCHIVER_VIEM_POLLING_INTERVAL_MS' - | 'ARCHIVER_MAX_LOGS' - | 'SEQ_TX_POLLING_INTERVAL_MS' - | 'SEQ_MAX_TX_PER_BLOCK' - | 'SEQ_MIN_TX_PER_BLOCK' - | 'SEQ_MIN_SECONDS_BETWEEN_BLOCKS' - | 'SEQ_MAX_SECONDS_BETWEEN_BLOCKS' - | 'COINBASE' - | 'FEE_RECIPIENT' - | 'ACVM_WORKING_DIRECTORY' - | 'ACVM_BINARY_PATH' - | 'SEQ_ALLOWED_SETUP_FN' - | 'SEQ_ALLOWED_TEARDOWN_FN' - | 'SEQ_MAX_BLOCK_SIZE_IN_BYTES' - | 'ENFORCE_FEES' - | 'SEQ_PUBLISHER_PRIVATE_KEY' - | 'SEQ_REQUIRED_CONFIRMATIONS' - | 'SEQ_PUBLISH_RETRY_INTERVAL_MS' - | 'VERSION' - | 'SEQ_DISABLED' - | 'PROVER_DISABLED' - | 'PROVER_REAL_PROOFS' + | 'P2P_QUERY_FOR_IP' + | 'P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS' + | 'P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS' + | 'P2P_SEVERE_PEER_PENALTY_BLOCK_LENGTH' + | 'P2P_TCP_ANNOUNCE_ADDR' + | 'P2P_TX_POOL_KEEP_PROVEN_FOR' + | 'P2P_TX_PROTOCOL' + | 'P2P_UDP_ANNOUNCE_ADDR' + | 'PEER_ID_PRIVATE_KEY' + | 'PROOF_VERIFIER_L1_START_BLOCK' + | 'PROOF_VERIFIER_POLL_INTERVAL_MS' + | 'PROVER_AGENT_CONCURRENCY' | 'PROVER_AGENT_ENABLED' | 'PROVER_AGENT_POLL_INTERVAL_MS' - | 'PROVER_AGENT_CONCURRENCY' - | 'PROVER_JOB_TIMEOUT_MS' - | 'PROVER_JOB_POLL_INTERVAL_MS' + | 'PROVER_COORDINATION_NODE_URL' + | 'PROVER_DISABLED' | 'PROVER_ID' - | 'WS_L2_BLOCK_QUEUE_SIZE' - | 'WS_PROVEN_BLOCKS_ONLY' + | 'PROVER_JOB_POLL_INTERVAL_MS' + | 'PROVER_JOB_TIMEOUT_MS' + | 'PROVER_NODE_DISABLE_AUTOMATIC_PROVING' + | 'PROVER_NODE_MAX_PENDING_JOBS' | 'PROVER_PUBLISH_RETRY_INTERVAL_MS' | 'PROVER_PUBLISHER_PRIVATE_KEY' + | 'PROVER_REAL_PROOFS' | 'PROVER_REQUIRED_CONFIRMATIONS' | 'PROVER_TEST_DELAY_MS' - | 'TX_PROVIDER_NODE_URL' - | 'TXE_PORT' - | 'LOG_JSON' - | 'BOT_PXE_URL' - | 'BOT_PRIVATE_KEY' - | 'BOT_RECIPIENT_ENCRYPTION_SECRET' - | 'BOT_TOKEN_SALT' - | 'BOT_TX_INTERVAL_SECONDS' - | 'BOT_PRIVATE_TRANSFERS_PER_TX' - | 'BOT_PUBLIC_TRANSFERS_PER_TX' - | 'BOT_FEE_PAYMENT_METHOD' - | 'BOT_NO_START' - | 'BOT_TX_MINED_WAIT_SECONDS' - | 'BOT_NO_WAIT_FOR_TRANSFERS' - | 'BOT_MAX_PENDING_TXS' - | 'BOT_SKIP_PUBLIC_SIMULATION' - | 'BOT_L2_GAS_LIMIT' - | 'BOT_DA_GAS_LIMIT' | 'PXE_BLOCK_POLLING_INTERVAL_MS' - | 'PXE_L2_STARTING_BLOCK' | 'PXE_DATA_DIRECTORY' - | 'BB_BINARY_PATH' - | 'BB_WORKING_DIRECTORY' - | 'BB_SKIP_CLEANUP' + | 'PXE_L2_STARTING_BLOCK' | 'PXE_PROVER_ENABLED' - | 'BOT_FOLLOW_CHAIN' - | 'BOT_FLUSH_SETUP_TRANSACTIONS' - | 'BOT_TOKEN_CONTRACT' - | 'VALIDATOR_PRIVATE_KEY' - | 'VALIDATOR_DISABLED' - | 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS' + | 'REGISTRY_CONTRACT_ADDRESS' + | 'ROLLUP_CONTRACT_ADDRESS' + | 'SEQ_ALLOWED_SETUP_FN' + | 'SEQ_ALLOWED_TEARDOWN_FN' + | 'SEQ_DISABLED' + | 'SEQ_MAX_BLOCK_SIZE_IN_BYTES' + | 'SEQ_MAX_SECONDS_BETWEEN_BLOCKS' + | 'SEQ_MAX_TX_PER_BLOCK' + | 'SEQ_MIN_SECONDS_BETWEEN_BLOCKS' + | 'SEQ_MIN_TX_PER_BLOCK' + | 'SEQ_PUBLISH_RETRY_INTERVAL_MS' + | 'SEQ_PUBLISHER_PRIVATE_KEY' + | 'SEQ_REQUIRED_CONFIRMATIONS' + | 'SEQ_TX_POLLING_INTERVAL_MS' + | 'TCP_LISTEN_ADDR' + | 'TELEMETRY' + | 'TEST_ACCOUNTS' + | 'TX_GOSSIP_VERSION' + | 'TXE_PORT' + | 'UDP_LISTEN_ADDR' | 'VALIDATOR_ATTESTATIONS_POOLING_INTERVAL_MS' - | 'PROVER_NODE_DISABLE_AUTOMATIC_PROVING' - | 'PROVER_NODE_MAX_PENDING_JOBS' - | 'PROOF_VERIFIER_POLL_INTERVAL_MS' - | 'PROOF_VERIFIER_L1_START_BLOCK' - | 'LOG_LEVEL' - | 'DEBUG'; + | 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS' + | 'VALIDATOR_DISABLED' + | 'VALIDATOR_PRIVATE_KEY' + | 'VERSION' + | 'WS_BLOCK_CHECK_INTERVAL_MS' + | 'WS_L2_BLOCK_QUEUE_SIZE' + | 'WS_PROVEN_BLOCKS_ONLY'; diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index f07c0fe8ed6..aaafccba9f1 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -6,9 +6,11 @@ import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; +import { InMemoryAttestationPool } from '../attestation_pool/memory_attestation_pool.js'; import { P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; +import { MemoryEpochProofQuotePool } from '../epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js'; import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; @@ -19,17 +21,22 @@ export * from './p2p_client.js'; export const createP2PClient = async ( _config: P2PConfig & DataStoreConfig, - attestationsPool: AttestationPool, - epochProofQuotePool: EpochProofQuotePool, l2BlockSource: L2BlockSource, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, telemetry: TelemetryClient = new NoopTelemetryClient(), - deps: { txPool?: TxPool; store?: AztecKVStore } = {}, + deps: { + txPool?: TxPool; + store?: AztecKVStore; + attestationsPool?: AttestationPool; + epochProofQuotePool?: EpochProofQuotePool; + } = {}, ) => { let config = { ..._config }; const store = deps.store ?? (await createStore('p2p', config, createDebugLogger('aztec:p2p:lmdb'))); const txPool = deps.txPool ?? new AztecKVTxPool(store, telemetry); + const attestationsPool = deps.attestationsPool ?? new InMemoryAttestationPool(); + const epochProofQuotePool = deps.epochProofQuotePool ?? new MemoryEpochProofQuotePool(); let p2pService; diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts index ffd539f5e80..9d83e09c156 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts @@ -146,18 +146,11 @@ describe('Req Resp p2p client integration', () => { kvStore = openTmpStore(); const deps = { txPool: txPool as unknown as TxPool, + attestationPool: attestationPool as unknown as AttestationPool, + epochProofQuotePool: epochProofQuotePool as unknown as EpochProofQuotePool, store: kvStore, }; - const client = await createP2PClient( - config, - attestationPool as unknown as AttestationPool, - epochProofQuotePool as unknown as EpochProofQuotePool, - blockSource, - proofVerifier, - worldStateSynchronizer, - undefined, - deps, - ); + const client = await createP2PClient(config, blockSource, proofVerifier, worldStateSynchronizer, undefined, deps); await client.start(); clients.push(client); diff --git a/yarn-project/prover-client/package.json b/yarn-project/prover-client/package.json index 636bdcac423..9a99cddb3d8 100644 --- a/yarn-project/prover-client/package.json +++ b/yarn-project/prover-client/package.json @@ -96,4 +96,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +} diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts index d7229c80afe..7da91540f0c 100644 --- a/yarn-project/prover-node/src/config.ts +++ b/yarn-project/prover-node/src/config.ts @@ -16,14 +16,18 @@ import { } from '@aztec/sequencer-client'; import { type WorldStateConfig, getWorldStateConfigFromEnv, worldStateConfigMappings } from '@aztec/world-state'; -import { type TxProviderConfig, getTxProviderConfigFromEnv, txProviderConfigMappings } from './tx-provider/config.js'; +import { + type ProverCoordinationConfig, + getTxProviderConfigFromEnv, + proverCoordinationConfigMappings, +} from './prover-coordination/config.js'; export type ProverNodeConfig = ArchiverConfig & ProverClientConfig & WorldStateConfig & PublisherConfig & TxSenderConfig & - TxProviderConfig & { + ProverCoordinationConfig & { proverNodeDisableAutomaticProving?: boolean; proverNodeMaxPendingJobs?: number; }; @@ -49,7 +53,7 @@ export const proverNodeConfigMappings: ConfigMappingsType = { ...worldStateConfigMappings, ...getPublisherConfigMappings('PROVER'), ...getTxSenderConfigMappings('PROVER'), - ...txProviderConfigMappings, + ...proverCoordinationConfigMappings, ...specificProverNodeConfigMappings, }; diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 7e9c31e8cbf..088a7cb32ac 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -9,9 +9,9 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { createWorldStateSynchronizer } from '@aztec/world-state'; import { type ProverNodeConfig } from './config.js'; +import { AztecNodeProverCoordination } from './prover-coordination/aztec-node-prover-coordination.js'; +import { createProverCoordination } from './prover-coordination/factory.js'; import { ProverNode } from './prover-node.js'; -import { AztecNodeTxProvider } from './tx-provider/aztec-node-tx-provider.js'; -import { createTxProvider } from './tx-provider/factory.js'; /** Creates a new prover node given a config. */ export async function createProverNode( @@ -40,8 +40,8 @@ export async function createProverNode( const publisher = new L1Publisher(config, telemetry); const txProvider = deps.aztecNodeTxProvider - ? new AztecNodeTxProvider(deps.aztecNodeTxProvider) - : createTxProvider(config); + ? new AztecNodeProverCoordination(deps.aztecNodeTxProvider) + : createProverCoordination(config); return new ProverNode( prover!, diff --git a/yarn-project/prover-node/src/job/block-proving-job.ts b/yarn-project/prover-node/src/job/block-proving-job.ts index a9fb9c822d8..2b1dd95c171 100644 --- a/yarn-project/prover-node/src/job/block-proving-job.ts +++ b/yarn-project/prover-node/src/job/block-proving-job.ts @@ -6,9 +6,9 @@ import { type L2BlockSource, PROVING_STATUS, type ProcessedTx, + type ProverCoordination, type Tx, type TxHash, - type TxProvider, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; @@ -35,7 +35,7 @@ export class BlockProvingJob { private publisher: L1Publisher, private l2BlockSource: L2BlockSource, private l1ToL2MessageSource: L1ToL2MessageSource, - private txProvider: TxProvider, + private coordination: ProverCoordination, private metrics: ProverNodeMetrics, private cleanUp: (job: BlockProvingJob) => Promise = () => Promise.resolve(), ) { @@ -142,7 +142,7 @@ export class BlockProvingJob { private async getTxs(txHashes: TxHash[]): Promise { const txs = await Promise.all( - txHashes.map(txHash => this.txProvider.getTxByHash(txHash).then(tx => [txHash, tx] as const)), + txHashes.map(txHash => this.coordination.getTxByHash(txHash).then(tx => [txHash, tx] as const)), ); const notFound = txs.filter(([_, tx]) => !tx); if (notFound.length) { diff --git a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts b/yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts similarity index 52% rename from yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts rename to yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts index 8e5dc75db18..3152cd2ebf8 100644 --- a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts +++ b/yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts @@ -1,7 +1,7 @@ -import { type AztecNode, type EpochProofQuote, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; +import type { AztecNode, EpochProofQuote, ProverCoordination, Tx, TxHash } from '@aztec/circuit-types'; -/** Implements TxProvider by querying an Aztec node for the txs. */ -export class AztecNodeTxProvider implements TxProvider { +/** Implements ProverCoordinator by wrapping an Aztec node */ +export class AztecNodeProverCoordination implements ProverCoordination { constructor(private node: AztecNode) {} getTxByHash(txHash: TxHash): Promise { diff --git a/yarn-project/prover-node/src/prover-coordination/config.ts b/yarn-project/prover-node/src/prover-coordination/config.ts new file mode 100644 index 00000000000..7940c803e96 --- /dev/null +++ b/yarn-project/prover-node/src/prover-coordination/config.ts @@ -0,0 +1,17 @@ +import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config'; + +export type ProverCoordinationConfig = { + proverCoordinationNodeUrl: string | undefined; +}; + +export const proverCoordinationConfigMappings: ConfigMappingsType = { + proverCoordinationNodeUrl: { + env: 'PROVER_COORDINATION_NODE_URL', + description: 'The URL of the tx provider node', + parseEnv: (val: string) => val, + }, +}; + +export function getTxProviderConfigFromEnv(): ProverCoordinationConfig { + return getConfigFromMappings(proverCoordinationConfigMappings); +} diff --git a/yarn-project/prover-node/src/prover-coordination/factory.ts b/yarn-project/prover-node/src/prover-coordination/factory.ts new file mode 100644 index 00000000000..71e5ba8a95e --- /dev/null +++ b/yarn-project/prover-node/src/prover-coordination/factory.ts @@ -0,0 +1,13 @@ +import { type ProverCoordination, createAztecNodeClient } from '@aztec/circuit-types'; + +import { AztecNodeProverCoordination } from './aztec-node-prover-coordination.js'; +import { type ProverCoordinationConfig } from './config.js'; + +export function createProverCoordination(config: ProverCoordinationConfig): ProverCoordination { + if (config.proverCoordinationNodeUrl) { + const node = createAztecNodeClient(config.proverCoordinationNodeUrl); + return new AztecNodeProverCoordination(node); + } else { + throw new Error(`Aztec Node URL for Tx Provider is not set.`); + } +} diff --git a/yarn-project/prover-node/src/tx-provider/index.ts b/yarn-project/prover-node/src/prover-coordination/index.ts similarity index 52% rename from yarn-project/prover-node/src/tx-provider/index.ts rename to yarn-project/prover-node/src/prover-coordination/index.ts index bac271dd877..7394c367754 100644 --- a/yarn-project/prover-node/src/tx-provider/index.ts +++ b/yarn-project/prover-node/src/prover-coordination/index.ts @@ -1,3 +1,3 @@ -export * from './aztec-node-tx-provider.js'; -export * from './factory.js'; +export * from './aztec-node-prover-coordination.js'; export * from './config.js'; +export * from './factory.js'; diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index e5cd74cfa32..3c9e5e6325d 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -3,7 +3,7 @@ import { type L2BlockSource, type MerkleTreeAdminOperations, type ProverClient, - type TxProvider, + type ProverCoordination, WorldStateRunningState, type WorldStateSynchronizer, } from '@aztec/circuit-types'; @@ -24,7 +24,7 @@ describe('prover-node', () => { let l1ToL2MessageSource: MockProxy; let contractDataSource: MockProxy; let worldState: MockProxy; - let txProvider: MockProxy; + let txProvider: MockProxy; let simulator: MockProxy; let proverNode: TestProverNode; @@ -43,7 +43,7 @@ describe('prover-node', () => { l1ToL2MessageSource = mock(); contractDataSource = mock(); worldState = mock(); - txProvider = mock(); + txProvider = mock(); simulator = mock(); const telemetryClient = new NoopTelemetryClient(); diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 42c30341512..952a32a0ca4 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -4,7 +4,7 @@ import type { L2BlockSource, MerkleTreeOperations, ProverClient, - TxProvider, + ProverCoordination, WorldStateSynchronizer, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -37,7 +37,7 @@ export class ProverNode { private l1ToL2MessageSource: L1ToL2MessageSource, private contractDataSource: ContractDataSource, private worldState: WorldStateSynchronizer, - private txProvider: TxProvider, + private coordination: ProverCoordination, private simulator: SimulationProvider, private telemetryClient: TelemetryClient, options: { pollingIntervalMs?: number; disableAutomaticProving?: boolean; maxPendingJobs?: number } = {}, @@ -127,7 +127,7 @@ export class ProverNode { } public sendEpochProofQuote(quote: EpochProofQuote): Promise { - return this.txProvider.addEpochProofQuote(quote); + return this.coordination.addEpochProofQuote(quote); } /** @@ -208,7 +208,7 @@ export class ProverNode { this.publisher, this.l2BlockSource, this.l1ToL2MessageSource, - this.txProvider, + this.coordination, this.metrics, cleanUp, ); diff --git a/yarn-project/prover-node/src/tx-provider/config.ts b/yarn-project/prover-node/src/tx-provider/config.ts deleted file mode 100644 index 5fc9ed9465d..00000000000 --- a/yarn-project/prover-node/src/tx-provider/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config'; - -export type TxProviderConfig = { - txProviderNodeUrl: string | undefined; -}; - -export const txProviderConfigMappings: ConfigMappingsType = { - txProviderNodeUrl: { - env: 'TX_PROVIDER_NODE_URL', - description: 'The URL of the tx provider node', - parseEnv: (val: string) => val, - }, -}; - -export function getTxProviderConfigFromEnv(): TxProviderConfig { - return getConfigFromMappings(txProviderConfigMappings); -} diff --git a/yarn-project/prover-node/src/tx-provider/factory.ts b/yarn-project/prover-node/src/tx-provider/factory.ts deleted file mode 100644 index e17d13e00c0..00000000000 --- a/yarn-project/prover-node/src/tx-provider/factory.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type TxProvider, createAztecNodeClient } from '@aztec/circuit-types'; - -import { AztecNodeTxProvider } from './aztec-node-tx-provider.js'; -import { type TxProviderConfig } from './config.js'; - -export function createTxProvider(config: TxProviderConfig): TxProvider { - if (config.txProviderNodeUrl) { - const node = createAztecNodeClient(config.txProviderNodeUrl); - return new AztecNodeTxProvider(node); - } else { - throw new Error(`Aztec Node URL for Tx Provider is not set.`); - } -} From 4273d2a191bbf70747926928a5f0203bae3d12d5 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 11:39:33 +0000 Subject: [PATCH 09/43] Initial work --- l1-contracts/src/core/Rollup.sol | 260 ++++++++++-------- l1-contracts/src/core/interfaces/IRollup.sol | 12 + .../core/sequencer_selection/ILeonidas.sol | 2 + .../src/core/sequencer_selection/Leonidas.sol | 11 + .../archiver/src/archiver/data_retrieval.ts | 4 +- yarn-project/circuit-types/src/mocks.ts | 22 ++ .../prover_coordination/epoch_proof_quote.ts | 11 + .../e2e_json_coordination.test.ts | 215 ++++++++++++++- 8 files changed, 416 insertions(+), 121 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index d9d26527873..ab298034887 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -157,60 +157,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { vkTreeRoot = _vkTreeRoot; } - function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) - external - override(IRollup) - { - uint256 currentSlot = getCurrentSlot(); - address currentProposer = getCurrentProposer(); - uint256 epochToProve = getEpochToProve(); - - if (currentProposer != address(0) && currentProposer != msg.sender) { - revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); - } - - if (_quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); - } - - if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { - revert Errors.Rollup__NotInClaimPhase( - currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS - ); - } - - // if the epoch to prove is not the one that has been claimed, - // then whatever is in the proofClaim is stale - if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { - revert Errors.Rollup__ProofRightAlreadyClaimed(); - } - - if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { - revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount - ); - } - - if (_quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); - } - - // We don't currently unstake, - // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. - // Blocked on submitting epoch proofs to this contract. - address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount); - - proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epochToProve, - basisPointFee: _quote.basisPointFee, - bondAmount: _quote.bondAmount, - bondProvider: bondProvider, - proposerClaimant: msg.sender - }); - - emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot); - } - /** * @notice Publishes the body and propose the block * @dev `eth_log_handlers` rely on this function @@ -221,68 +167,17 @@ contract Rollup is Leonidas, IRollup, ITestRollup { * @param _signatures - Signatures from the validators * @param _body - The body of the L2 block */ - function propose( + function proposeAndClaim( bytes calldata _header, bytes32 _archive, bytes32 _blockHash, bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, - bytes calldata _body + bytes calldata _body, + DataStructures.EpochProofQuote calldata _quote ) external override(IRollup) { - if (_canPrune()) { - _prune(); - } - bytes32 txsEffectsHash = TxsDecoder.decode(_body); - - // Decode and validate header - HeaderLib.Header memory header = HeaderLib.decode(_header); - - bytes32 digest = keccak256(abi.encode(_archive, _txHashes)); - setupEpoch(); - _validateHeader({ - _header: header, - _signatures: _signatures, - _digest: digest, - _currentTime: block.timestamp, - _txEffectsHash: txsEffectsHash, - _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) - }); - - uint256 blockNumber = ++tips.pendingBlockNumber; - - blocks[blockNumber] = BlockLog({ - archive: _archive, - blockHash: _blockHash, - slotNumber: header.globalVariables.slotNumber.toUint128() - }); - - // @note The block number here will always be >=1 as the genesis block is at 0 - bytes32 inHash = INBOX.consume(blockNumber); - if (header.contentCommitment.inHash != inHash) { - revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); - } - - // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim - // Min size = smallest path of the rollup tree + 1 - (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); - uint256 l2ToL1TreeMinHeight = min + 1; - OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight); - - emit L2BlockProposed(blockNumber, _archive); - - // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. - if (blockNumber <= assumeProvenThroughBlockNumber) { - tips.provenBlockNumber = blockNumber; - - if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { - // @note This will currently fail if there are insufficient funds in the bridge - // which WILL happen for the old version after an upgrade where the bridge follow. - // Consider allowing a failure. See #7938. - FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); - } - - emit L2ProofVerified(blockNumber, "CHEAT"); - } + propose(_header, _archive, _blockHash, _txHashes, _signatures, _body); + claimEpochProofRight(_quote); } /** @@ -509,6 +404,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags); } + function nextEpochToClaim() external view override(IRollup) returns (uint256) { + uint256 epochClaimed = proofClaim.epochToProve; + if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) { + return 0; + } + return 1 + epochClaimed; + } + function computeTxsEffectsHash(bytes calldata _body) external pure @@ -518,6 +421,141 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return TxsDecoder.decode(_body); } + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) + public + override(IRollup) + { + validateEpochProofRightClaim(_quote); + + uint256 currentSlot = getCurrentSlot(); + uint256 epochToProve = getEpochToProve(); + + // We don't currently unstake, + // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. + // Blocked on submitting epoch proofs to this contract. + address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount); + + proofClaim = DataStructures.EpochProofClaim({ + epochToProve: epochToProve, + basisPointFee: _quote.basisPointFee, + bondAmount: _quote.bondAmount, + bondProvider: bondProvider, + proposerClaimant: msg.sender + }); + + emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot); + } + + /** + * @notice Publishes the body and propose the block + * @dev `eth_log_handlers` rely on this function + * + * @param _header - The L2 block header + * @param _archive - A root of the archive tree after the L2 block is applied + * @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit + * @param _signatures - Signatures from the validators + * @param _body - The body of the L2 block + */ + function propose( + bytes calldata _header, + bytes32 _archive, + bytes32 _blockHash, + bytes32[] memory _txHashes, + SignatureLib.Signature[] memory _signatures, + bytes calldata _body + ) public override(IRollup) { + if (_canPrune()) { + _prune(); + } + bytes32 txsEffectsHash = TxsDecoder.decode(_body); + + // Decode and validate header + HeaderLib.Header memory header = HeaderLib.decode(_header); + + bytes32 digest = keccak256(abi.encode(_archive, _txHashes)); + setupEpoch(); + _validateHeader({ + _header: header, + _signatures: _signatures, + _digest: digest, + _currentTime: block.timestamp, + _txEffectsHash: txsEffectsHash, + _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) + }); + + uint256 blockNumber = ++tips.pendingBlockNumber; + + blocks[blockNumber] = BlockLog({ + archive: _archive, + blockHash: _blockHash, + slotNumber: header.globalVariables.slotNumber.toUint128() + }); + + // @note The block number here will always be >=1 as the genesis block is at 0 + bytes32 inHash = INBOX.consume(blockNumber); + if (header.contentCommitment.inHash != inHash) { + revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); + } + + // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim + // Min size = smallest path of the rollup tree + 1 + (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); + uint256 l2ToL1TreeMinHeight = min + 1; + OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight); + + emit L2BlockProposed(blockNumber, _archive); + + // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. + if (blockNumber <= assumeProvenThroughBlockNumber) { + tips.provenBlockNumber = blockNumber; + + if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { + // @note This will currently fail if there are insufficient funds in the bridge + // which WILL happen for the old version after an upgrade where the bridge follow. + // Consider allowing a failure. See #7938. + FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); + } + + emit L2ProofVerified(blockNumber, "CHEAT"); + } + } + + function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) public view override(IRollup) { + uint256 currentSlot = getCurrentSlot(); + address currentProposer = getCurrentProposer(); + uint256 epochToProve = getEpochToProve(); + + if (currentProposer != address(0) && currentProposer != msg.sender) { + revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); + } + + if (_quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); + } + + if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { + revert Errors.Rollup__NotInClaimPhase( + currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS + ); + } + + // if the epoch to prove is not the one that has been claimed, + // then whatever is in the proofClaim is stale + if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { + revert Errors.Rollup__ProofRightAlreadyClaimed(); + } + + if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + revert Errors.Rollup__InsufficientBondAmount( + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount + ); + } + + if (_quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); + } + } + /** * @notice Get the current archive root * @@ -548,7 +586,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { if (tips.provenBlockNumber == tips.pendingBlockNumber) { revert Errors.Rollup__NoEpochToProve(); } else { - return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber); + return getEpochAt(getTimestampForSlot(blocks[getProvenBlockNumber() + 1].slotNumber)); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 464f4c93a96..cdbb656f6d2 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -39,6 +39,16 @@ interface IRollup { bytes calldata _body ) external; + function proposeAndClaim( + bytes calldata _header, + bytes32 _archive, + bytes32 _blockHash, + bytes32[] memory _txHashes, + SignatureLib.Signature[] memory _signatures, + bytes calldata _body, + DataStructures.EpochProofQuote calldata _quote + ) external; + function submitBlockRootProof( bytes calldata _header, bytes32 _archive, @@ -94,5 +104,7 @@ interface IRollup { function getProvenBlockNumber() external view returns (uint256); function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (uint256); + function nextEpochToClaim() external view returns (uint256); + function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) external view; function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); } diff --git a/l1-contracts/src/core/sequencer_selection/ILeonidas.sol b/l1-contracts/src/core/sequencer_selection/ILeonidas.sol index 68a58572236..97da9b9fcbe 100644 --- a/l1-contracts/src/core/sequencer_selection/ILeonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/ILeonidas.sol @@ -30,4 +30,6 @@ interface ILeonidas { function getEpochAt(uint256 _ts) external view returns (uint256); function getSlotAt(uint256 _ts) external view returns (uint256); + + function getEpochAtSlot(uint256 _slotNumber) external view returns (uint256); } diff --git a/l1-contracts/src/core/sequencer_selection/Leonidas.sol b/l1-contracts/src/core/sequencer_selection/Leonidas.sol index 8a90052500f..ccd26851252 100644 --- a/l1-contracts/src/core/sequencer_selection/Leonidas.sol +++ b/l1-contracts/src/core/sequencer_selection/Leonidas.sol @@ -314,6 +314,17 @@ contract Leonidas is Ownable, ILeonidas { return _ts < GENESIS_TIME ? 0 : (_ts - GENESIS_TIME) / SLOT_DURATION; } + /** + * @notice Computes the epoch at a specific slot + * + * @param _slotNumber - The slot number to compute the epoch for + * + * @return The computed epoch + */ + function getEpochAtSlot(uint256 _slotNumber) public pure override(ILeonidas) returns (uint256) { + return _slotNumber / EPOCH_DURATION; + } + /** * @notice Adds a validator to the set WITHOUT setting up the epoch * @param _validator - The validator to add diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index 1e79352fc81..6e287293c12 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -134,7 +134,9 @@ async function getBlockFromRollupTx( data, }); - if (!(functionName === 'propose')) { + const allowedMethods = ['propose', 'proposeAndClaim']; + + if (!allowedMethods.includes(functionName)) { throw new Error(`Unexpected method called ${functionName}`); } const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex]; diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 3edf74adb19..f4f25708587 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -2,6 +2,7 @@ import { AztecAddress, CallContext, ClientIvcProof, + type EthAddress, GasSettings, LogHash, MAX_ENCRYPTED_LOGS_PER_TX, @@ -28,11 +29,14 @@ import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd, times } from '@aztec/foundation/collection'; import { randomBytes } from '@aztec/foundation/crypto'; +import { Signature } from '@aztec/foundation/eth-signature'; import { Fr } from '@aztec/foundation/fields'; import { type ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; import { EncryptedNoteTxL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js'; import { ExtendedNote, UniqueNote } from './notes/index.js'; +import { EpochProofQuote } from './prover_coordination/epoch_proof_quote.js'; +import { EpochProofQuotePayload } from './prover_coordination/epoch_proof_quote_payload.js'; import { PublicExecutionRequest } from './public_execution_request.js'; import { NestedProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js'; @@ -223,6 +227,24 @@ export const mockSimulatedTx = (seed = 1, hasLogs = true) => { return new SimulatedTx(tx, dec, output); }; +export const mockEpochProofQuote = ( + epochToProve: bigint, + validUntilSlot: bigint, + bondAmount: bigint, + rollupAddress: EthAddress, + basisPointFee: number, +) => { + const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( + epochToProve, + validUntilSlot, + bondAmount, + rollupAddress, + basisPointFee, + ); + const sig: Signature = Signature.empty(); + return new EpochProofQuote(quotePayload, sig); +}; + export const randomContractArtifact = (): ContractArtifact => ({ name: randomBytes(4).toString('hex'), functions: [], diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index d5c2f40e296..0e88a7ebffa 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -52,4 +52,15 @@ export class EpochProofQuote extends Gossipable { return this.sender; } + + toViemArgs() { + return { + epochToProve: this.payload.epochToProve, + validUntilSlot: this.payload.validUntilSlot, + bondAmount: this.payload.bondAmount, + rollupAddress: this.payload.rollupAddress, + basisPointFee: this.payload.basisPointFee, + signature: this.signature.toViemSignature(), + }; + } } diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts index 89172315e9a..fa8478d0908 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -1,7 +1,13 @@ -import { type DebugLogger, createDebugLogger } from '@aztec/aztec.js'; -import { makeRandomEpochProofQuote } from '@aztec/p2p'; +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { type AccountWalletWithSecretKey, type DebugLogger, EthCheatCodes, createDebugLogger } from '@aztec/aztec.js'; +import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types'; +import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js'; +import { times } from '@aztec/foundation/collection'; +import { RollupAbi } from '@aztec/l1-artifacts'; +import { StatefulTestContract } from '@aztec/noir-contracts.js'; import { beforeAll } from '@jest/globals'; +import { type PublicClient, getAddress, getContract } from 'viem'; import { type ISnapshotManager, @@ -16,6 +22,12 @@ import { // the coordination through L1 between the sequencer and the prover node. describe('e2e_prover_node', () => { let ctx: SubsystemsContext; + let wallet: AccountWalletWithSecretKey; + let recipient: AztecAddress; + let contract: StatefulTestContract; + let rollupContract: any; + let publicClient: PublicClient; + let cc: EthCheatCodes; let logger: DebugLogger; let snapshotManager: ISnapshotManager; @@ -24,17 +36,202 @@ describe('e2e_prover_node', () => { logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); - await snapshotManager.snapshot('setup', addAccounts(2, logger)); + logger.info(`1`); + + await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { + const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1)); + await Promise.all(accountManagers.map(a => a.register())); + const wallets = await Promise.all(accountManagers.map(a => a.getWallet())); + wallets.forEach((w, i) => logger.verbose(`Wallet ${i} address: ${w.getAddress()}`)); + wallet = wallets[0]; + recipient = wallets[1].getAddress(); + }); + + await snapshotManager.snapshot( + 'deploy-test-contract', + async () => { + const owner = wallet.getAddress(); + const contract = await StatefulTestContract.deploy(wallet, owner, owner, 42).send().deployed(); + return { contractAddress: contract.address }; + }, + async ({ contractAddress }) => { + contract = await StatefulTestContract.at(contractAddress, wallet); + }, + ); ctx = await snapshotManager.setup(); + + await ctx.proverNode.stop(); + + cc = new EthCheatCodes(ctx.aztecNodeConfig.l1RpcUrl); + + publicClient = ctx.deployL1ContractsValues.publicClient; + rollupContract = getContract({ + address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()), + abi: RollupAbi, + client: ctx.deployL1ContractsValues.walletClient, + }); }); - it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { - const { quote } = makeRandomEpochProofQuote(); + // it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { + // const { quote } = makeRandomEpochProofQuote(); + + // await ctx.proverNode.sendEpochProofQuote(quote); + // const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); + // expect(receivedQuotes.length).toBe(1); + // expect(receivedQuotes[0]).toEqual(quote); + // }); + + const expectProofClaimOnL1 = async (quote: EpochProofQuote, _: EthAddress) => { + const claimFromContract = await rollupContract.read.proofClaim(); + expect(claimFromContract[0]).toEqual(quote.payload.epochToProve); + expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee)); + expect(claimFromContract[2]).toEqual(quote.payload.bondAmount); + //expect(claimFromContract[4]).toEqual(proposer.toString()); + }; + + const getL1Timestamp = async () => { + return BigInt((await publicClient.getBlock()).timestamp); + }; + + const getSlot = async () => { + const ts = await getL1Timestamp(); + return await rollupContract.read.getSlotAt([ts]); + }; + + const getEpoch = async () => { + const slotNumber = await getSlot(); + return await rollupContract.read.getEpochAtSlot([slotNumber]); + }; + + const getPendingBlockNumber = async () => { + return await rollupContract.read.getPendingBlockNumber(); + }; + + const getProvenBlockNumber = async () => { + return await rollupContract.read.getProvenBlockNumber(); + }; + + const getEpochToProve = async () => { + return await rollupContract.read.getEpochToProve(); + }; + + const getTimestampForSlot = async (slotNumber: bigint) => { + return await rollupContract.read.getTimestampForSlot([slotNumber]); + }; + + const verifyQuote = async (quote: EpochProofQuote) => { + try { + const args = [quote.toViemArgs()] as const; + await rollupContract.read.validateEpochProofRightClaim(args); + logger.info('QUOTE VERIFIED'); + } catch (error) { + console.log(error); + } + }; + + const logState = async () => { + logger.info(`Pending block: ${await getPendingBlockNumber()}`); + logger.info(`Proven block: ${await getProvenBlockNumber()}`); + logger.info(`Slot number: ${await getSlot()}`); + logger.info(`Epoch number: ${await getEpoch()}`); + logger.info(`Epoch to prove ${await getEpochToProve()}`); + }; + + const advanceToNextEpoch = async () => { + const slot = await getSlot(); + const slotsUntilNextEpoch = BigInt(AZTEC_EPOCH_DURATION) - (slot % BigInt(AZTEC_EPOCH_DURATION)) + 1n; + const timeToNextEpoch = slotsUntilNextEpoch * BigInt(AZTEC_SLOT_DURATION); + logger.info(`SLOTS TO NEXT EPOCH ${slotsUntilNextEpoch}`); + const l1Timestamp = await getL1Timestamp(); + await cc.warp(Number(l1Timestamp + timeToNextEpoch)); + await logState(); + }; + + it('Sequencer selects best valid proving quote for each block', async () => { + // We want to create a set of proving quotes, some valid and some invalid + // The sequencer should select the cheapest valid quote when it proposes the block + logger.info(`Start`); + + // Here we are creating a proof quote for epoch 0, this will NOT get used yet + const quoteForEpoch0 = mockEpochProofQuote( + 0n, // epoch 0 + BigInt(AZTEC_EPOCH_DURATION + 10), // valid until slot 10 into epoch 1 + 10000n, + EthAddress.random(), + 1, + ); + + // Send in the quote + await ctx.proverNode.sendEpochProofQuote(quoteForEpoch0); + + // Build a block, this should NOT use the above quote as it is for the current epoch (0) + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + await logState(); + + const epoch0BlockNumber = await getPendingBlockNumber(); + + // Verify that the claim state on L1 is unitialised + const uninitialisedProofClaim = mockEpochProofQuote( + 0n, // epoch 0 + BigInt(0), + 0n, + EthAddress.random(), + 0, + ); + + // The rollup contract should have an uninitialised proof claim struct + await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.random()); + + // Now go to epoch 1 + await advanceToNextEpoch(); + + const blockSlot = await getSlot(); + + logger.info(`TIMESTAMP FOR SLOT: ${await getTimestampForSlot(blockSlot)}`); + + await logState(); + + // Build a block in epoch 1, we should see the quote for epoch 0 submitted earlier published to L1 + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + // Check it was published + await expectProofClaimOnL1(quoteForEpoch0, EthAddress.random()); + + // now 'prove' epoch 0 + await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); + + logger.info(`SET PROVEN BLOCK NUMBER`); + + await logState(); + + // Now go to epoch 2 + await advanceToNextEpoch(); + + const currentSlot = await getSlot(); + + // Now create a number of quotes, some valid some invalid for epoch 1, the lowest priced valid quote should be chosen + const validQuotes = times(3, (i: number) => + mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i), + ); + + // Check the L1 verification of this quote + await verifyQuote(validQuotes[0]); + + const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1); + + const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes]; + + //await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); + + // now build another block and we should see the best valid quote being published + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + const expectedQuote = validQuotes[0]; - await ctx.proverNode.sendEpochProofQuote(quote); - const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); - expect(receivedQuotes.length).toBe(1); - expect(receivedQuotes[0]).toEqual(quote); + await expectProofClaimOnL1(expectedQuote, EthAddress.random()); }); }); From c280ad90834f96b618d34e70202c7c51f876d23a Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 16:17:25 +0000 Subject: [PATCH 10/43] Passing test --- l1-contracts/src/core/Rollup.sol | 3 +- yarn-project/aztec.js/src/index.ts | 2 + .../e2e_json_coordination.test.ts | 83 ++--- .../src/publisher/l1-publisher.ts | 252 ++++++++----- .../src/sequencer/sequencer.test.ts | 336 +++++++++++++++++- .../src/sequencer/sequencer.ts | 53 ++- 6 files changed, 589 insertions(+), 140 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index ab298034887..90735f04ed6 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -625,7 +625,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } uint256 currentSlot = getCurrentSlot(); - uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); + uint256 oldestPendingEpoch = + getEpochAt(getTimestampForSlot(blocks[tips.provenBlockNumber + 1].slotNumber)); uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index 3c012b20f28..d782a191768 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -133,9 +133,11 @@ export { createAztecNodeClient, merkleTreeIds, mockTx, + mockEpochProofQuote, TaggedLog, L1NotePayload, L1EventPayload, + EpochProofQuote, } from '@aztec/circuit-types'; export { NodeInfo } from '@aztec/types/interfaces'; diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts index fa8478d0908..8ebe827b0bc 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -1,6 +1,12 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; -import { type AccountWalletWithSecretKey, type DebugLogger, EthCheatCodes, createDebugLogger } from '@aztec/aztec.js'; -import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types'; +import { + type AccountWalletWithSecretKey, + type DebugLogger, + type EpochProofQuote, + EthCheatCodes, + createDebugLogger, + mockEpochProofQuote, +} from '@aztec/aztec.js'; import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js'; import { times } from '@aztec/foundation/collection'; import { RollupAbi } from '@aztec/l1-artifacts'; @@ -28,6 +34,7 @@ describe('e2e_prover_node', () => { let rollupContract: any; let publicClient: PublicClient; let cc: EthCheatCodes; + let publisherAddress: EthAddress; let logger: DebugLogger; let snapshotManager: ISnapshotManager; @@ -36,8 +43,6 @@ describe('e2e_prover_node', () => { logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); - logger.info(`1`); - await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1)); await Promise.all(accountManagers.map(a => a.register())); @@ -66,6 +71,7 @@ describe('e2e_prover_node', () => { cc = new EthCheatCodes(ctx.aztecNodeConfig.l1RpcUrl); publicClient = ctx.deployL1ContractsValues.publicClient; + publisherAddress = EthAddress.fromString(ctx.deployL1ContractsValues.walletClient.account.address); rollupContract = getContract({ address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()), abi: RollupAbi, @@ -73,21 +79,12 @@ describe('e2e_prover_node', () => { }); }); - // it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { - // const { quote } = makeRandomEpochProofQuote(); - - // await ctx.proverNode.sendEpochProofQuote(quote); - // const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); - // expect(receivedQuotes.length).toBe(1); - // expect(receivedQuotes[0]).toEqual(quote); - // }); - - const expectProofClaimOnL1 = async (quote: EpochProofQuote, _: EthAddress) => { + const expectProofClaimOnL1 = async (quote: EpochProofQuote, proposerAddress: EthAddress) => { const claimFromContract = await rollupContract.read.proofClaim(); expect(claimFromContract[0]).toEqual(quote.payload.epochToProve); expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee)); expect(claimFromContract[2]).toEqual(quote.payload.bondAmount); - //expect(claimFromContract[4]).toEqual(proposer.toString()); + expect(claimFromContract[4]).toEqual(proposerAddress.toChecksumString()); }; const getL1Timestamp = async () => { @@ -116,20 +113,6 @@ describe('e2e_prover_node', () => { return await rollupContract.read.getEpochToProve(); }; - const getTimestampForSlot = async (slotNumber: bigint) => { - return await rollupContract.read.getTimestampForSlot([slotNumber]); - }; - - const verifyQuote = async (quote: EpochProofQuote) => { - try { - const args = [quote.toViemArgs()] as const; - await rollupContract.read.validateEpochProofRightClaim(args); - logger.info('QUOTE VERIFIED'); - } catch (error) { - console.log(error); - } - }; - const logState = async () => { logger.info(`Pending block: ${await getPendingBlockNumber()}`); logger.info(`Proven block: ${await getProvenBlockNumber()}`); @@ -142,7 +125,6 @@ describe('e2e_prover_node', () => { const slot = await getSlot(); const slotsUntilNextEpoch = BigInt(AZTEC_EPOCH_DURATION) - (slot % BigInt(AZTEC_EPOCH_DURATION)) + 1n; const timeToNextEpoch = slotsUntilNextEpoch * BigInt(AZTEC_SLOT_DURATION); - logger.info(`SLOTS TO NEXT EPOCH ${slotsUntilNextEpoch}`); const l1Timestamp = await getL1Timestamp(); await cc.warp(Number(l1Timestamp + timeToNextEpoch)); await logState(); @@ -151,7 +133,6 @@ describe('e2e_prover_node', () => { it('Sequencer selects best valid proving quote for each block', async () => { // We want to create a set of proving quotes, some valid and some invalid // The sequencer should select the cheapest valid quote when it proposes the block - logger.info(`Start`); // Here we are creating a proof quote for epoch 0, this will NOT get used yet const quoteForEpoch0 = mockEpochProofQuote( @@ -182,28 +163,24 @@ describe('e2e_prover_node', () => { ); // The rollup contract should have an uninitialised proof claim struct - await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.random()); + await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.ZERO); // Now go to epoch 1 await advanceToNextEpoch(); - const blockSlot = await getSlot(); - - logger.info(`TIMESTAMP FOR SLOT: ${await getTimestampForSlot(blockSlot)}`); - await logState(); // Build a block in epoch 1, we should see the quote for epoch 0 submitted earlier published to L1 await contract.methods.create_note(recipient, recipient, 10).send().wait(); + const epoch1BlockNumber = await getPendingBlockNumber(); + // Check it was published - await expectProofClaimOnL1(quoteForEpoch0, EthAddress.random()); + await expectProofClaimOnL1(quoteForEpoch0, publisherAddress); // now 'prove' epoch 0 await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); - logger.info(`SET PROVEN BLOCK NUMBER`); - await logState(); // Now go to epoch 2 @@ -216,22 +193,38 @@ describe('e2e_prover_node', () => { mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i), ); - // Check the L1 verification of this quote - await verifyQuote(validQuotes[0]); - const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1); const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2); - const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes]; + const proofQuoteInsufficientBond = mockEpochProofQuote(1n, currentSlot + 4n, 0n, EthAddress.random(), 3); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInsufficientBond]; - //await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); + await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); // now build another block and we should see the best valid quote being published await contract.methods.create_note(recipient, recipient, 10).send().wait(); const expectedQuote = validQuotes[0]; - await expectProofClaimOnL1(expectedQuote, EthAddress.random()); + await expectProofClaimOnL1(expectedQuote, publisherAddress); + + // building another block should succeed, we should not try and submit another quote + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + await expectProofClaimOnL1(expectedQuote, publisherAddress); + + // now 'prove' epoch 1 + await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch1BlockNumber)]); + + // Now go to epoch 3 + await advanceToNextEpoch(); + + // now build another block and we should see that no claim is published as nothing is valid + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + // The quote state on L1 is the same as before + await expectProofClaimOnL1(expectedQuote, publisherAddress); }); }); diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 6df5560d3b2..733ce1fa25b 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -1,4 +1,10 @@ -import { ConsensusPayload, type L2Block, type TxHash, getHashedSignaturePayload } from '@aztec/circuit-types'; +import { + ConsensusPayload, + type EpochProofQuote, + type L2Block, + type TxHash, + getHashedSignaturePayload, +} from '@aztec/circuit-types'; import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats'; import { ETHEREUM_SLOT_DURATION, EthAddress, type Header, type Proof } from '@aztec/circuits.js'; import { createEthereumChain } from '@aztec/ethereum'; @@ -116,6 +122,7 @@ export class L1Publisher { private account: PrivateKeyAccount; public static PROPOSE_GAS_GUESS: bigint = 500_000n; + public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = 600_000n; constructor(config: TxSenderConfig & PublisherConfig, client: TelemetryClient) { this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000; @@ -162,6 +169,33 @@ export class L1Publisher { return [slot, blockNumber]; } + /** + * @notice Queries 'proofClaim' on the Rollup to determine if we are able to claim the given epoch for proving + * Only checks to see if the given epoch is claimed + * + * @param epochNumber - The epoch being queried + * @return True or False + */ + public async canClaimEpoch(epochNumber: bigint): Promise { + const nextToBeClaimed = await this.rollupContract.read.nextEpochToClaim(); + return epochNumber == nextToBeClaimed; + } + + public async getEpochForSlotNumber(slotNumber: bigint): Promise { + return await this.rollupContract.read.getEpochAtSlot([slotNumber]); + } + + public async validateProofQuote(quote: EpochProofQuote): Promise { + const args = [quote.toViemArgs()] as const; + try { + await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account }); + } catch (err) { + //console.log(err); + return undefined; + } + return quote; + } + /** * @notice Will call `validateHeader` to make sure that it is possible to propose * @@ -229,7 +263,12 @@ export class L1Publisher { * @param block - L2 block to propose. * @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise. */ - public async proposeL2Block(block: L2Block, attestations?: Signature[], txHashes?: TxHash[]): Promise { + public async proposeL2Block( + block: L2Block, + attestations?: Signature[], + txHashes?: TxHash[], + proofQuote?: EpochProofQuote, + ): Promise { const ctx = { blockNumber: block.number, slotNumber: block.header.globalVariables.slotNumber.toBigInt(), @@ -249,53 +288,58 @@ export class L1Publisher { }; // Publish body and propose block (if not already published) - if (!this.interrupted) { - const timer = new Timer(); - - // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available - // This means that we can avoid the simulation issues in later checks. - // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which - // make time consistency checks break. - await this.validateBlockForSubmission(block.header, { - digest: digest.toBuffer(), - signatures: attestations ?? [], - }); + if (this.interrupted) { + this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx); + return false; + } - const txHash = await this.sendProposeTx(proposeTxArgs); + const timer = new Timer(); - if (!txHash) { - this.log.info(`Failed to publish block ${block.number} to L1`, ctx); - return false; - } + // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available + // This means that we can avoid the simulation issues in later checks. + // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which + // make time consistency checks break. + await this.validateBlockForSubmission(block.header, { + digest: digest.toBuffer(), + signatures: attestations ?? [], + }); - const receipt = await this.getTransactionReceipt(txHash); - if (!receipt) { - this.log.info(`Failed to get receipt for tx ${txHash}`, ctx); - return false; - } + this.log.verbose(`Submitting propose transaction with `); - // Tx was mined successfully - if (receipt.status) { - const tx = await this.getTransactionStats(txHash); - const stats: L1PublishBlockStats = { - ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), - ...pick(tx!, 'calldataGas', 'calldataSize'), - ...block.getStats(), - eventName: 'rollup-published-to-l1', - }; - this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx }); - this.metrics.recordProcessBlockTx(timer.ms(), stats); + const txHash = proofQuote + ? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote) + : await this.sendProposeTx(proposeTxArgs); - return true; - } + if (!txHash) { + this.log.info(`Failed to publish block ${block.number} to L1`, ctx); + return false; + } - this.metrics.recordFailedTx('process'); + const receipt = await this.getTransactionReceipt(txHash); + if (!receipt) { + this.log.info(`Failed to get receipt for tx ${txHash}`, ctx); + return false; + } - this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx); - await this.sleepOrInterrupted(); + // Tx was mined successfully + if (receipt.status) { + const tx = await this.getTransactionStats(txHash); + const stats: L1PublishBlockStats = { + ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), + ...pick(tx!, 'calldataGas', 'calldataSize'), + ...block.getStats(), + eventName: 'rollup-published-to-l1', + }; + this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx }); + this.metrics.recordProcessBlockTx(timer.ms(), stats); + + return true; } - this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx); + this.metrics.recordFailedTx('process'); + + this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx); + await this.sleepOrInterrupted(); return false; } @@ -395,47 +439,95 @@ export class L1Publisher { } private async sendProposeTx(encodedData: L1ProcessArgs): Promise { - if (!this.interrupted) { - try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); + if (this.interrupted) { + return; + } + try { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - ] as const; - - return await this.rollupContract.write.propose(args, { - account: this.account, - gas: gasGuesstimate, - }); - } catch (err) { - prettyLogVeimError(err, this.log); - this.log.error(`Rollup publish failed`, err); - return undefined; - } + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return await this.rollupContract.write.propose(args, { + account: this.account, + gas: gasGuesstimate, + }); + } catch (err) { + prettyLogVeimError(err, this.log); + this.log.error(`Rollup publish failed`, err); + return undefined; + } + } + + private async sendProposeAndClaimTx(encodedData: L1ProcessArgs, quote: EpochProofQuote): Promise { + if (this.interrupted) { + return; + } + try { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); + + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + quote.toViemArgs(), + ] as const; + + return await this.rollupContract.write.proposeAndClaim(args, { + account: this.account, + gas: gasGuesstimate, + }); + } catch (err) { + prettyLogVeimError(err, this.log); + this.log.error(`Rollup publish failed`, err); + return undefined; } } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 004b862bacc..4374041a19e 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -3,6 +3,7 @@ import { BlockProposal, type BlockSimulator, ConsensusPayload, + type EpochProofQuote, type L1ToL2MessageSource, L2Block, type L2BlockSource, @@ -18,9 +19,11 @@ import { WorldStateRunningState, type WorldStateSynchronizer, makeProcessedTx, + mockEpochProofQuote, mockTxForRollup, } from '@aztec/circuit-types'; import { + AZTEC_EPOCH_DURATION, AztecAddress, EthAddress, Fr, @@ -119,12 +122,12 @@ describe('sequencer', () => { blockSimulator = mock(); p2p = mock({ - getStatus: () => Promise.resolve({ state: P2PClientState.IDLE, syncedToL2Block: lastBlockNumber }), + getStatus: mockFn().mockResolvedValue({ state: P2PClientState.IDLE, syncedToL2Block: lastBlockNumber }), }); worldState = mock({ getLatest: () => merkleTreeOps, - status: () => Promise.resolve({ state: WorldStateRunningState.IDLE, syncedToL2Block: lastBlockNumber }), + status: mockFn().mockResolvedValue({ state: WorldStateRunningState.IDLE, syncedToL2Block: lastBlockNumber }), }); publicProcessor = mock({ @@ -145,7 +148,7 @@ describe('sequencer', () => { l1ToL2MessageSource = mock({ getL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), - getBlockNumber: () => Promise.resolve(lastBlockNumber), + getBlockNumber: mockFn().mockResolvedValue(lastBlockNumber), }); // all txs use the same allowed FPC class @@ -208,7 +211,7 @@ describe('sequencer', () => { ); // Ok, we have an issue that we never actually call the process L2 block expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash]); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -257,7 +260,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash]); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -300,7 +303,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(p2p.deleteTxs).toHaveBeenCalledWith([doubleSpendTx.getTxHash()]); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -339,7 +342,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(p2p.deleteTxs).toHaveBeenCalledWith([invalidChainTx.getTxHash()]); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -380,7 +383,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -431,7 +434,7 @@ describe('sequencer', () => { Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), txHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), txHashes, undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -482,7 +485,7 @@ describe('sequencer', () => { Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), []); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [], undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -536,7 +539,7 @@ describe('sequencer', () => { ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), postFlushTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), postFlushTxHashes, undefined); expect(blockSimulator.cancelBlock).toHaveBeenCalledTimes(0); }); @@ -580,6 +583,317 @@ describe('sequencer', () => { expect(publisher.proposeL2Block).not.toHaveBeenCalled(); }); + + describe('Handling proof quotes', () => { + let txHash: TxHash; + let currentEpoch = 0n; + const setupForBlockNumber = (blockNumber: number) => { + currentEpoch = BigInt(blockNumber) / BigInt(AZTEC_EPOCH_DURATION); + // Create a new block and header + block = L2Block.random(blockNumber); + + mockedGlobalVariables = new GlobalVariables( + chainId, + version, + block.header.globalVariables.blockNumber, + block.header.globalVariables.slotNumber, + Fr.ZERO, + coinbase, + feeRecipient, + gasFees, + ); + + worldState.status.mockResolvedValue({ + state: WorldStateRunningState.IDLE, + syncedToL2Block: block.header.globalVariables.blockNumber.toNumber() - 1, + }); + + p2p.getStatus.mockResolvedValue({ + syncedToL2Block: block.header.globalVariables.blockNumber.toNumber() - 1, + state: P2PClientState.IDLE, + }); + + l2BlockSource.getBlockNumber.mockResolvedValue(block.header.globalVariables.blockNumber.toNumber() - 1); + + l1ToL2MessageSource.getBlockNumber.mockResolvedValue(block.header.globalVariables.blockNumber.toNumber() - 1); + + globalVariableBuilder.buildGlobalVariables.mockResolvedValue(mockedGlobalVariables); + + publisher.canProposeAtNextEthBlock.mockResolvedValue([ + block.header.globalVariables.slotNumber.toBigInt(), + block.header.globalVariables.blockNumber.toBigInt(), + ]); + + publisher.getEpochForSlotNumber.mockImplementation((slotNumber: bigint) => + Promise.resolve(slotNumber / BigInt(AZTEC_EPOCH_DURATION)), + ); + + const tx = mockTxForRollup(); + tx.data.constants.txContext.chainId = chainId; + txHash = tx.getTxHash(); + const result: ProvingSuccess = { + status: PROVING_STATUS.SUCCESS, + }; + const ticket: ProvingTicket = { + provingPromise: Promise.resolve(result), + }; + + p2p.getTxs.mockReturnValue([tx]); + blockSimulator.startNewBlock.mockResolvedValueOnce(ticket); + blockSimulator.finaliseBlock.mockResolvedValue({ block }); + }; + + it('submits a valid proof quote with a block', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote); + }); + + it('does not claim the epoch previous to the first', async () => { + const blockNumber = 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + 0n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote); + }); + + it('does not submit a quote with an expired slot number', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + // Slot number expired + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('does not submit a valid quote if unable to claim epoch', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockResolvedValue(false); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('does not submit an invalid quote', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(_ => Promise.resolve(undefined)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('only selects valid quotes', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + // Create 1 valid quote and 3 that have a higher fee but are invalid + const validProofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + const proofQuoteInvalidSlot = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + const proofQuoteInvalidEpoch = mockEpochProofQuote( + currentEpoch, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + // This is deemed invalid by the contract, we identify it by a fee of 2 + const proofQuoteInvalid = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 2, + ); + + const allQuotes = [validProofQuote, proofQuoteInvalidSlot, proofQuoteInvalidEpoch, proofQuoteInvalid]; + + p2p.getEpochProofQuotes.mockResolvedValue(allQuotes); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(p => + Promise.resolve(p.payload.basisPointFee === 2 ? undefined : p), + ); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validProofQuote); + }); + + it('selects the lowest cost valid quote', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + // Create 3 valid quotes with different fees. + // And 3 invalid quotes with lower fees + // We should select the lowest cost valid quote + const validQuotes = times(3, (i: number) => + mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 10 + i, + ), + ); + + const proofQuoteInvalidSlot = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 1, + ); + + const proofQuoteInvalidEpoch = mockEpochProofQuote( + currentEpoch, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + // This is deemed invalid by the contract, we identify it by it's fee + const proofQuoteInvalid = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 3, + ); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInvalid]; + + p2p.getEpochProofQuotes.mockResolvedValue(allQuotes); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(p => + Promise.resolve(p.payload.basisPointFee === 3 ? undefined : p), + ); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validQuotes[0]); + }); + }); }); class TestSubject extends Sequencer { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 69b8398d152..62021165d1d 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,5 +1,6 @@ import { type BlockAttestation, + type EpochProofQuote, type L1ToL2MessageSource, type L2Block, type L2BlockSource, @@ -404,6 +405,14 @@ export class Sequencer { const newGlobalVariables = proposalHeader.globalVariables; + // Kick off the process of collecting and validating proof quotes here so it runs alongside block building + const proofQuotePromise = this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt()).catch( + e => { + this.log.debug(`Failed to create proof claim quote ${e}`); + return undefined; + }, + ); + this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length); const workTimer = new Timer(); this.state = SequencerState.CREATING_BLOCK; @@ -488,8 +497,10 @@ export class Sequencer { const attestations = await this.collectAttestations(block, txHashes); this.log.verbose('Attestations collected'); + const proofQuote = await proofQuotePromise; + try { - await this.publishL2Block(block, attestations, txHashes); + await this.publishL2Block(block, attestations, txHashes, proofQuote); this.metrics.recordPublishedBlock(workDuration); this.log.info( `Submitted rollup block ${block.number} with ${ @@ -540,6 +551,37 @@ export class Sequencer { return orderAttestations(attestations, committee); } + protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise { + const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber); + if (epochForBlock < 1n) { + this.log.verbose(`First epoch has no claim`); + return undefined; + } + const epochToProve = epochForBlock - 1n; + const canClaim = await this.publisher.canClaimEpoch(epochToProve); + if (!canClaim) { + this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); + return undefined; + } + const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve); + this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`); + // ensure these quotes are still valid for the slot + const validQuotesPromise = Promise.all( + quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)), + ); + + const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q); + if (!validQuotes.length) { + this.log.verbose(`Failed to find any valid proof quotes`); + return undefined; + } + // pick the quote with the lowest fee + const sortedQuotes = validQuotes.sort( + (a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee, + ); + return sortedQuotes[0]; + } + /** * Publishes the L2Block to the rollup contract. * @param block - The L2Block to be published. @@ -547,11 +589,16 @@ export class Sequencer { @trackSpan('Sequencer.publishL2Block', block => ({ [Attributes.BLOCK_NUMBER]: block.number, })) - protected async publishL2Block(block: L2Block, attestations?: Signature[], txHashes?: TxHash[]) { + protected async publishL2Block( + block: L2Block, + attestations?: Signature[], + txHashes?: TxHash[], + proofQuote?: EpochProofQuote, + ) { // Publishes new block to the network and awaits the tx to be mined this.state = SequencerState.PUBLISHING_BLOCK; - const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes); + const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote); if (publishedL2Block) { this.lastPublishedBlock = block.number; } else { From 8f0ab894270f80002e47383f7bce4020b7b96c73 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:07:33 +0000 Subject: [PATCH 11/43] Merge fixes --- l1-contracts/src/core/Rollup.sol | 22 ++++++------- l1-contracts/src/core/interfaces/IRollup.sol | 2 +- l1-contracts/test/Rollup.t.sol | 34 ++++++++++---------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index f82e2163489..1a31e4c6a19 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -482,18 +482,18 @@ contract Rollup is Leonidas, IRollup, ITestRollup { // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); + PROOF_COMMITMENT_ESCROW.stakeBond(_quote.bondAmount, _quote.prover); proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, - basisPointFee: _quote.quote.basisPointFee, - bondAmount: _quote.quote.bondAmount, - bondProvider: _quote.quote.prover, + basisPointFee: _quote.basisPointFee, + bondAmount: _quote.bondAmount, + bondProvider: _quote.prover, proposerClaimant: msg.sender }); emit ProofRightClaimed( - epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot + epochToProve, _quote.prover, msg.sender, _quote.bondAmount, currentSlot ); } @@ -709,8 +709,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); } - if (_quote.quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); + if (_quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); } if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { @@ -725,14 +725,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Rollup__ProofRightAlreadyClaimed(); } - if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount ); } - if (_quote.quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot); + if (_quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index f0b0e468c8a..bf6c1eb805e 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -28,7 +28,7 @@ interface IRollup { function prune() external; - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) external; + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external; function propose( bytes calldata _header, diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 47395f930ed..def9b943b40 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -101,7 +101,7 @@ contract RollupTest is DecoderBase { // sanity check that proven/pending tip are at genesis vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(1); assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); @@ -109,7 +109,7 @@ contract RollupTest is DecoderBase { // empty slots do not move pending chain vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { @@ -122,7 +122,7 @@ contract RollupTest is DecoderBase { Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.quote.epochToProve ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { @@ -137,7 +137,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimPastValidUntil() public setUpFor("mixed_block_1") { @@ -150,7 +150,7 @@ contract RollupTest is DecoderBase { vm.expectRevert( abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.quote.validUntilSlot) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimSimple() public setUpFor("mixed_block_1") { @@ -162,7 +162,7 @@ contract RollupTest is DecoderBase { emit IRollup.ProofRightClaimed( quote.quote.epochToProve, address(0), address(this), quote.quote.bondAmount, 1 ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); ( uint256 epochToProve, @@ -185,14 +185,14 @@ contract RollupTest is DecoderBase { warpToL2Slot(1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(2); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); // warp to epoch 1 warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); @@ -200,7 +200,7 @@ contract RollupTest is DecoderBase { // We should still be trying to prove epoch 0 in epoch 1 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); // still nothing to prune vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); @@ -219,7 +219,7 @@ contract RollupTest is DecoderBase { rollup.CLAIM_DURATION_IN_L2_SLOTS() ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { @@ -229,7 +229,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); @@ -244,18 +244,18 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); // We should still be trying to prove epoch 0 in epoch 2 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); rollup.prune(); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimAfterPrune() public setUpFor("mixed_block_1") { @@ -266,7 +266,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); @@ -282,7 +282,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount, Constants.AZTEC_EPOCH_DURATION * 2 ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { From 735fa3bc7ce8bf067e1adb2d9fcfe1d3adc0f811 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:28:19 +0000 Subject: [PATCH 12/43] More merge fixes --- l1-contracts/src/core/Rollup.sol | 28 +++++++-------- l1-contracts/src/core/interfaces/IRollup.sol | 11 +++--- l1-contracts/test/Rollup.t.sol | 34 +++++++++---------- .../src/publisher/l1-publisher.ts | 13 ++----- .../src/sequencer/sequencer.test.ts | 26 ++++---------- .../src/sequencer/sequencer.ts | 4 +-- 6 files changed, 48 insertions(+), 68 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 1a31e4c6a19..c6bf7e87a01 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -171,7 +171,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.EpochProofQuote calldata _quote + DataStructures.SignedEpochProofQuote calldata _quote ) external override(IRollup) { propose(_header, _archive, _blockHash, _txHashes, _signatures, _body); claimEpochProofRight(_quote); @@ -470,7 +470,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return TxsDecoder.decode(_body); } - function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) + function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) public override(IRollup) { @@ -482,18 +482,18 @@ contract Rollup is Leonidas, IRollup, ITestRollup { // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.bondAmount, _quote.prover); + PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, - basisPointFee: _quote.basisPointFee, - bondAmount: _quote.bondAmount, - bondProvider: _quote.prover, + basisPointFee: _quote.quote.basisPointFee, + bondAmount: _quote.quote.bondAmount, + bondProvider: _quote.quote.prover, proposerClaimant: msg.sender }); emit ProofRightClaimed( - epochToProve, _quote.prover, msg.sender, _quote.bondAmount, currentSlot + epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot ); } @@ -700,7 +700,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return publicInputs; } - function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) public view override(IRollup) { + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) public view override(IRollup) { uint256 currentSlot = getCurrentSlot(); address currentProposer = getCurrentProposer(); uint256 epochToProve = getEpochToProve(); @@ -709,8 +709,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); } - if (_quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); + if (_quote.quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); } if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { @@ -725,14 +725,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Rollup__ProofRightAlreadyClaimed(); } - if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount ); } - if (_quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); + if (_quote.quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index bf6c1eb805e..09b7e293fa5 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -28,7 +28,7 @@ interface IRollup { function prune() external; - function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external; + function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) external; function propose( bytes calldata _header, @@ -46,7 +46,7 @@ interface IRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.EpochProofQuote calldata _quote + DataStructures.SignedEpochProofQuote calldata _quote ) external; function submitBlockRootProof( @@ -114,13 +114,14 @@ interface IRollup { function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (uint256); function nextEpochToClaim() external view returns (uint256); - function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) external view; - function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); - + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) external view; function getEpochProofPublicInputs( uint256 _epochSize, bytes32[7] calldata _args, bytes32[64] calldata _fees, bytes calldata _aggregationObject ) external view returns (bytes32[] memory); + function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); + + } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index def9b943b40..47395f930ed 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -101,7 +101,7 @@ contract RollupTest is DecoderBase { // sanity check that proven/pending tip are at genesis vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(1); assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); @@ -109,7 +109,7 @@ contract RollupTest is DecoderBase { // empty slots do not move pending chain vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { @@ -122,7 +122,7 @@ contract RollupTest is DecoderBase { Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.quote.epochToProve ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { @@ -137,7 +137,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimPastValidUntil() public setUpFor("mixed_block_1") { @@ -150,7 +150,7 @@ contract RollupTest is DecoderBase { vm.expectRevert( abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.quote.validUntilSlot) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimSimple() public setUpFor("mixed_block_1") { @@ -162,7 +162,7 @@ contract RollupTest is DecoderBase { emit IRollup.ProofRightClaimed( quote.quote.epochToProve, address(0), address(this), quote.quote.bondAmount, 1 ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); ( uint256 epochToProve, @@ -185,14 +185,14 @@ contract RollupTest is DecoderBase { warpToL2Slot(1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(2); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); // warp to epoch 1 warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); @@ -200,7 +200,7 @@ contract RollupTest is DecoderBase { // We should still be trying to prove epoch 0 in epoch 1 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); // still nothing to prune vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); @@ -219,7 +219,7 @@ contract RollupTest is DecoderBase { rollup.CLAIM_DURATION_IN_L2_SLOTS() ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { @@ -229,7 +229,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); @@ -244,18 +244,18 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); // We should still be trying to prove epoch 0 in epoch 2 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); rollup.prune(); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimAfterPrune() public setUpFor("mixed_block_1") { @@ -266,7 +266,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); @@ -282,7 +282,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount, Constants.AZTEC_EPOCH_DURATION * 2 ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 4c888a0bf0c..718d3f0b58a 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -185,16 +185,8 @@ export class L1Publisher { return [slot, blockNumber]; } - /** - * @notice Queries 'proofClaim' on the Rollup to determine if we are able to claim the given epoch for proving - * Only checks to see if the given epoch is claimed - * - * @param epochNumber - The epoch being queried - * @return True or False - */ - public async canClaimEpoch(epochNumber: bigint): Promise { - const nextToBeClaimed = await this.rollupContract.read.nextEpochToClaim(); - return epochNumber == nextToBeClaimed; + public async nextEpochToClaim(): Promise { + return await this.rollupContract.read.nextEpochToClaim(); } public async getEpochForSlotNumber(slotNumber: bigint): Promise { @@ -206,7 +198,6 @@ export class L1Publisher { try { await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account }); } catch (err) { - //console.log(err); return undefined; } return quote; diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 4c1d79c1540..e474abd2cc6 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -660,9 +660,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -686,9 +684,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -713,9 +709,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -739,7 +733,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockResolvedValue(false); + publisher.nextEpochToClaim.mockResolvedValue(currentEpoch); await sequencer.initialSync(); await sequencer.work(); @@ -765,9 +759,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation(_ => Promise.resolve(undefined)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -823,9 +815,7 @@ describe('sequencer', () => { ); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -885,9 +875,7 @@ describe('sequencer', () => { ); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index f6a04198071..b799d176211 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -558,8 +558,8 @@ export class Sequencer { return undefined; } const epochToProve = epochForBlock - 1n; - const canClaim = await this.publisher.canClaimEpoch(epochToProve); - if (!canClaim) { + const canClaim = await this.publisher.nextEpochToClaim(); + if (canClaim != epochToProve) { this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); return undefined; } From 9de33b07a7375305be7b578279a4ab13b85d2aba Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:40:18 +0000 Subject: [PATCH 13/43] More merge fixes --- .../prover_coordination/epoch_proof_quote.test.ts | 2 +- .../src/prover_coordination/epoch_proof_quote.ts | 12 +++++++----- .../prover_coordination/epoch_proof_quote_payload.ts | 8 ++++---- .../p2p/src/epoch_proof_quote_pool/test_utils.ts | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts index c62995559ef..049845921e1 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts @@ -11,7 +11,7 @@ describe('epoch proof quote', () => { basisPointFee: 5000, bondAmount: 1000000000000000000n, epochToProve: 42n, - rollupAddress: EthAddress.random(), + prover: EthAddress.random(), validUntilSlot: 100n, }); diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index 0e88a7ebffa..8839b257ff7 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -55,11 +55,13 @@ export class EpochProofQuote extends Gossipable { toViemArgs() { return { - epochToProve: this.payload.epochToProve, - validUntilSlot: this.payload.validUntilSlot, - bondAmount: this.payload.bondAmount, - rollupAddress: this.payload.rollupAddress, - basisPointFee: this.payload.basisPointFee, + quote: { + epochToProve: this.payload.epochToProve, + validUntilSlot: this.payload.validUntilSlot, + bondAmount: this.payload.bondAmount, + prover: this.payload.prover.toString(), + basisPointFee: this.payload.basisPointFee, + }, signature: this.signature.toViemSignature(), }; } diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts index bd0c92fcdc7..be70356f7d8 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts @@ -11,7 +11,7 @@ export class EpochProofQuotePayload implements Signable { public readonly epochToProve: bigint, public readonly validUntilSlot: bigint, public readonly bondAmount: bigint, - public readonly rollupAddress: EthAddress, + public readonly prover: EthAddress, public readonly basisPointFee: number, ) {} @@ -20,7 +20,7 @@ export class EpochProofQuotePayload implements Signable { fields.epochToProve, fields.validUntilSlot, fields.bondAmount, - fields.rollupAddress, + fields.prover, fields.basisPointFee, ] as const; } @@ -45,7 +45,7 @@ export class EpochProofQuotePayload implements Signable { fields.epochToProve, fields.validUntilSlot, fields.bondAmount, - fields.rollupAddress, + fields.prover, fields.basisPointFee, ); } @@ -56,7 +56,7 @@ export class EpochProofQuotePayload implements Signable { this.epochToProve, this.validUntilSlot, this.bondAmount, - this.rollupAddress.toString(), + this.prover.toString(), this.basisPointFee, ] as const); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts index a12e10d85fa..0847e254014 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts @@ -7,7 +7,7 @@ export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { basisPointFee: randomInt(10000), bondAmount: 1000000000000000000n, epochToProve: randomBigInt(1000000n), - rollupAddress: EthAddress.random(), + prover: EthAddress.random(), validUntilSlot: randomBigInt(1000000n), }); } From ce6a728b1c61019a9bbbfd4455c9ee0b44fa5664 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:58:32 +0000 Subject: [PATCH 14/43] Refactoring --- .../src/publisher/l1-publisher.ts | 100 +++++++----------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 718d3f0b58a..475f73c5432 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -513,40 +513,46 @@ export class L1Publisher { } } + private async prepareProposeTx(encodedData: L1ProcessArgs, gasGuess: bigint) { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); + + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + gasGuess; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return { args, gasGuesstimate }; + } + private async sendProposeTx(encodedData: L1ProcessArgs): Promise { if (this.interrupted) { return; } try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); - - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - ] as const; + const { args, gasGuesstimate } = await this.prepareProposeTx(encodedData, L1Publisher.PROPOSE_GAS_GUESS); return await this.rollupContract.write.propose(args, { account: this.account, @@ -564,38 +570,12 @@ export class L1Publisher { return; } try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); - - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - quote.toViemArgs(), - ] as const; + const { args, gasGuesstimate } = await this.prepareProposeTx( + encodedData, + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS, + ); - return await this.rollupContract.write.proposeAndClaim(args, { + return await this.rollupContract.write.proposeAndClaim([...args, quote.toViemArgs()], { account: this.account, gas: gasGuesstimate, }); From 431a1c65fbac81640c00240fe24571cd89aa5de1 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 21:04:22 +0000 Subject: [PATCH 15/43] Formatting --- l1-contracts/src/core/Rollup.sol | 8 ++++++-- l1-contracts/src/core/interfaces/IRollup.sol | 6 +++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index c6bf7e87a01..bade58dec0a 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -456,7 +456,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { function nextEpochToClaim() external view override(IRollup) returns (uint256) { uint256 epochClaimed = proofClaim.epochToProve; if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) { - return 0; + return 0; } return 1 + epochClaimed; } @@ -700,7 +700,11 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return publicInputs; } - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) public view override(IRollup) { + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) + public + view + override(IRollup) + { uint256 currentSlot = getCurrentSlot(); address currentProposer = getCurrentProposer(); uint256 epochToProve = getEpochToProve(); diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 09b7e293fa5..141e319cc2c 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -114,7 +114,9 @@ interface IRollup { function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (uint256); function nextEpochToClaim() external view returns (uint256); - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) external view; + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) + external + view; function getEpochProofPublicInputs( uint256 _epochSize, bytes32[7] calldata _args, @@ -122,6 +124,4 @@ interface IRollup { bytes calldata _aggregationObject ) external view returns (bytes32[] memory); function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); - - } From cfb609dcb85e2c866a5ff24ff3a57c17b236df3a Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 21:12:14 +0000 Subject: [PATCH 16/43] Comments --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index b799d176211..eb1ac9b3b26 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -552,20 +552,25 @@ export class Sequencer { } protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise { + // Find out which epoch we are currently in const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber); if (epochForBlock < 1n) { + // It's the 0th epoch, nothing to be proven yet this.log.verbose(`First epoch has no claim`); return undefined; } const epochToProve = epochForBlock - 1n; + // Find out the next epoch that can be claimed const canClaim = await this.publisher.nextEpochToClaim(); if (canClaim != epochToProve) { + // It's not the one we are looking to claim this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); return undefined; } + // Get quotes for the epoch to be proven const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve); this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`); - // ensure these quotes are still valid for the slot + // ensure these quotes are still valid for the slot and have the contract validate them const validQuotesPromise = Promise.all( quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)), ); From 6144011179191e39776a1b59c6cfe68b418eaf17 Mon Sep 17 00:00:00 2001 From: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:24:47 +0100 Subject: [PATCH 17/43] Update yarn-project/sequencer-client/src/sequencer/sequencer.ts Co-authored-by: Santiago Palladino --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index eb1ac9b3b26..6b5cf45ba47 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -564,7 +564,7 @@ export class Sequencer { const canClaim = await this.publisher.nextEpochToClaim(); if (canClaim != epochToProve) { // It's not the one we are looking to claim - this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); + this.log.verbose(`Unable to claim previous epoch (${canClaim} != ${epochToProve})`); return undefined; } // Get quotes for the epoch to be proven From 2380ac774a6bacdde7897ccdb42662e9290441fa Mon Sep 17 00:00:00 2001 From: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:25:00 +0100 Subject: [PATCH 18/43] Update yarn-project/sequencer-client/src/sequencer/sequencer.ts Co-authored-by: Santiago Palladino --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 6b5cf45ba47..6113a8e9814 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -408,7 +408,7 @@ export class Sequencer { // Kick off the process of collecting and validating proof quotes here so it runs alongside block building const proofQuotePromise = this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt()).catch( e => { - this.log.debug(`Failed to create proof claim quote ${e}`); + this.log.warn(`Failed to create proof claim quote ${e}`); return undefined; }, ); From 901c47d734645bd6f04ad1953d03c96719dbfb2c Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 08:34:03 +0000 Subject: [PATCH 19/43] Review changes --- yarn-project/sequencer-client/src/publisher/l1-publisher.ts | 6 +++--- yarn-project/sequencer-client/src/publisher/utils.ts | 2 +- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 475f73c5432..ee1ed2db56b 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -40,7 +40,7 @@ import type * as chains from 'viem/chains'; import { type PublisherConfig, type TxSenderConfig } from './config.js'; import { L1PublisherMetrics } from './l1-publisher-metrics.js'; -import { prettyLogVeimError } from './utils.js'; +import { prettyLogViemError } from './utils.js'; /** * Stats for a sent transaction. @@ -559,7 +559,7 @@ export class L1Publisher { gas: gasGuesstimate, }); } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); this.log.error(`Rollup publish failed`, err); return undefined; } @@ -580,7 +580,7 @@ export class L1Publisher { gas: gasGuesstimate, }); } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); this.log.error(`Rollup publish failed`, err); return undefined; } diff --git a/yarn-project/sequencer-client/src/publisher/utils.ts b/yarn-project/sequencer-client/src/publisher/utils.ts index 13842102a2c..8889aa0998c 100644 --- a/yarn-project/sequencer-client/src/publisher/utils.ts +++ b/yarn-project/sequencer-client/src/publisher/utils.ts @@ -2,7 +2,7 @@ import { type Logger } from '@aztec/foundation/log'; import { BaseError, ContractFunctionRevertedError } from 'viem'; -export function prettyLogVeimError(err: any, logger: Logger) { +export function prettyLogViemError(err: any, logger: Logger) { if (err instanceof BaseError) { const revertError = err.walk(err => err instanceof ContractFunctionRevertedError); if (revertError instanceof ContractFunctionRevertedError) { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 6113a8e9814..5e7bd9a4ae3 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -35,7 +35,7 @@ import { type ValidatorClient } from '@aztec/validator-client'; import { type BlockBuilderFactory } from '../block_builder/index.js'; import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; import { type L1Publisher } from '../publisher/l1-publisher.js'; -import { prettyLogVeimError } from '../publisher/utils.js'; +import { prettyLogViemError } from '../publisher/utils.js'; import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js'; import { type SequencerConfig } from './config.js'; import { SequencerMetrics } from './metrics.js'; @@ -312,7 +312,7 @@ export class Sequencer { this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`); return slot; } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); throw err; } } From a2d793dc2e16f7b0694930349cecbb8df109485c Mon Sep 17 00:00:00 2001 From: Mitch Date: Mon, 23 Sep 2024 13:33:18 -0400 Subject: [PATCH 20/43] feat: basic epoch proof quote pool and interface on aztec node --- .../aztec-node/src/aztec-node/server.ts | 15 +++- .../src/aztec_node/rpc/aztec_node_client.ts | 8 +-- yarn-project/circuit-types/src/index.ts | 5 +- .../src/interfaces/aztec-node.ts | 71 ++++++++++--------- .../src/interfaces/tx-provider.ts | 7 ++ .../src/prover_coordination/index.ts | 2 + .../e2e_json_coordination.test.ts | 40 +++++++++++ yarn-project/p2p/src/client/index.ts | 12 +++- .../p2p/src/client/p2p_client.test.ts | 20 ++++-- yarn-project/p2p/src/client/p2p_client.ts | 48 ++++++++++--- .../epoch_proof_quote_pool.ts | 6 ++ .../p2p/src/epoch_proof_quote_pool/index.ts | 3 + .../memory_epoch_proof_quote_pool.test.ts | 21 ++++++ .../memory_epoch_proof_quote_pool.ts | 18 +++++ .../src/epoch_proof_quote_pool/test_utils.ts | 25 +++++++ yarn-project/p2p/src/index.ts | 7 +- .../reqresp/p2p_client.integration.test.ts | 8 +++ yarn-project/prover-node/src/prover-node.ts | 19 +++-- .../src/tx-provider/aztec-node-tx-provider.ts | 6 +- 19 files changed, 272 insertions(+), 69 deletions(-) create mode 100644 yarn-project/circuit-types/src/prover_coordination/index.ts create mode 100644 yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/index.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts create mode 100644 yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index c8a34e7a1ab..b6e5d3ddc40 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -3,6 +3,7 @@ import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover'; import { type AztecNode, type ClientProtocolCircuitVerifier, + type EpochProofQuote, type FromLogType, type GetUnencryptedLogsResponse, type L1ToL2MessageSource, @@ -59,6 +60,7 @@ import { DataTxValidator, DoubleSpendTxValidator, InMemoryAttestationPool, + MemoryEpochProofQuotePool, MetadataTxValidator, type P2P, TxProofValidator, @@ -121,6 +123,14 @@ export class AztecNodeService implements AztecNode { this.log.info(message); } + addEpochProofQuote(quote: EpochProofQuote): Promise { + return Promise.resolve(this.p2pClient.broadcastEpochProofQuote(quote)); + } + + getEpochProofQuotes(epoch: bigint): Promise { + return this.p2pClient.getEpochProofQuotes(epoch); + } + /** * initializes the Aztec Node, wait for component to sync. * @param config - The configuration to be used by the aztec node. @@ -154,6 +164,7 @@ export class AztecNodeService implements AztecNode { const p2pClient = await createP2PClient( config, new InMemoryAttestationPool(), + new MemoryEpochProofQuotePool(), archiver, proofVerifier, worldStateSynchronizer, @@ -229,8 +240,8 @@ export class AztecNodeService implements AztecNode { * Method to determine if the node is ready to accept transactions. * @returns - Flag indicating the readiness for tx submission. */ - public async isReady() { - return (await this.p2pClient.isReady()) ?? false; + public isReady() { + return Promise.resolve(this.p2pClient.isReady() ?? false); } /** diff --git a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts index 73c2da7cab0..e38f04f9f9e 100644 --- a/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts +++ b/yarn-project/circuit-types/src/aztec_node/rpc/aztec_node_client.ts @@ -31,19 +31,19 @@ export function createAztecNodeClient(url: string, fetch = defaultFetch): AztecN url, { AztecAddress, + Buffer32, EthAddress, + EventSelector, ExtendedUnencryptedL2Log, Fr, - EventSelector, FunctionSelector, Header, L2Block, - TxEffect, LogId, - TxHash, - Buffer32, PublicDataWitness, SiblingPath, + TxEffect, + TxHash, }, { EncryptedNoteL2BlockL2Logs, diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index 187539e9736..12c74f0c8f8 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -3,6 +3,7 @@ export * from './auth_witness.js'; export * from './aztec_node/rpc/index.js'; export * from './body.js'; export * from './function_call.js'; +export * from './global_variable_builder.js'; export * from './interfaces/index.js'; export * from './l2_block.js'; export * from './l2_block_downloader/index.js'; @@ -12,7 +13,9 @@ export * from './merkle_tree_id.js'; export * from './messaging/index.js'; export * from './mocks.js'; export * from './notes/index.js'; +export * from './p2p/index.js'; export * from './packed_values.js'; +export * from './prover_coordination/index.js'; export * from './public_data_witness.js'; export * from './public_data_write.js'; export * from './public_execution_request.js'; @@ -21,5 +24,3 @@ export * from './simulation_error.js'; export * from './tx/index.js'; export * from './tx_effect.js'; export * from './tx_execution_request.js'; -export * from './p2p/index.js'; -export * from './global_variable_builder.js'; diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index 16c64a1ac42..733982bb432 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -1,38 +1,33 @@ -import { - type ARCHIVE_HEIGHT, - type Header, - type L1_TO_L2_MSG_TREE_HEIGHT, - type NOTE_HASH_TREE_HEIGHT, - type NULLIFIER_TREE_HEIGHT, - type PUBLIC_DATA_TREE_HEIGHT, +import type { + ARCHIVE_HEIGHT, + Header, + L1_TO_L2_MSG_TREE_HEIGHT, + NOTE_HASH_TREE_HEIGHT, + NULLIFIER_TREE_HEIGHT, + PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/circuits.js'; -import { type L1ContractAddresses } from '@aztec/ethereum'; -import { type ContractArtifact } from '@aztec/foundation/abi'; -import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { type Fr } from '@aztec/foundation/fields'; -import { - type ContractClassPublic, - type ContractInstanceWithAddress, - type ProtocolContractAddresses, +import type { L1ContractAddresses } from '@aztec/ethereum'; +import type { ContractArtifact } from '@aztec/foundation/abi'; +import type { AztecAddress } from '@aztec/foundation/aztec-address'; +import type { Fr } from '@aztec/foundation/fields'; +import type { + ContractClassPublic, + ContractInstanceWithAddress, + ProtocolContractAddresses, } from '@aztec/types/contracts'; -import { type L2Block } from '../l2_block.js'; -import { - type FromLogType, - type GetUnencryptedLogsResponse, - type L2BlockL2Logs, - type LogFilter, - type LogType, -} from '../logs/index.js'; -import { type MerkleTreeId } from '../merkle_tree_id.js'; -import { type PublicDataWitness } from '../public_data_witness.js'; -import { type SiblingPath } from '../sibling_path/index.js'; -import { type PublicSimulationOutput, type Tx, type TxHash, type TxReceipt } from '../tx/index.js'; -import { type TxEffect } from '../tx_effect.js'; -import { type SequencerConfig } from './configs.js'; -import { type L2BlockNumber } from './l2_block_number.js'; -import { type NullifierMembershipWitness } from './nullifier_tree.js'; -import { type ProverConfig } from './prover-client.js'; +import type { L2Block } from '../l2_block.js'; +import type { FromLogType, GetUnencryptedLogsResponse, L2BlockL2Logs, LogFilter, LogType } from '../logs/index.js'; +import type { MerkleTreeId } from '../merkle_tree_id.js'; +import type { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; +import type { PublicDataWitness } from '../public_data_witness.js'; +import type { SiblingPath } from '../sibling_path/index.js'; +import type { PublicSimulationOutput, Tx, TxHash, TxReceipt } from '../tx/index.js'; +import type { TxEffect } from '../tx_effect.js'; +import type { SequencerConfig } from './configs.js'; +import type { L2BlockNumber } from './l2_block_number.js'; +import type { NullifierMembershipWitness } from './nullifier_tree.js'; +import type { ProverConfig } from './prover-client.js'; /** * The aztec node. @@ -356,4 +351,16 @@ export interface AztecNode { * Returns the ENR of this node for peer discovery, if available. */ getEncodedEnr(): Promise; + + /** + * Receives a quote for an epoch proof and stores it in its EpochProofQuotePool + * @param quote - The quote to store + */ + addEpochProofQuote(quote: EpochProofQuote): Promise; + + /** + * Returns the received quotes for a given epoch + * @param epoch - The epoch for which to get the quotes + */ + getEpochProofQuotes(epoch: bigint): Promise; } diff --git a/yarn-project/circuit-types/src/interfaces/tx-provider.ts b/yarn-project/circuit-types/src/interfaces/tx-provider.ts index 872f9ce0282..60b7f59e9b9 100644 --- a/yarn-project/circuit-types/src/interfaces/tx-provider.ts +++ b/yarn-project/circuit-types/src/interfaces/tx-provider.ts @@ -1,3 +1,4 @@ +import { type EpochProofQuote } from '../prover_coordination/index.js'; import { type Tx } from '../tx/tx.js'; import { type TxHash } from '../tx/tx_hash.js'; @@ -9,4 +10,10 @@ export interface TxProvider { * @returns The transaction, if found, 'undefined' otherwise. */ getTxByHash(txHash: TxHash): Promise; + + /** + * Receives a quote for an epoch proof and stores it in its EpochProofQuotePool + * @param quote - The quote to store + */ + addEpochProofQuote(quote: EpochProofQuote): Promise; } diff --git a/yarn-project/circuit-types/src/prover_coordination/index.ts b/yarn-project/circuit-types/src/prover_coordination/index.ts new file mode 100644 index 00000000000..331978ec556 --- /dev/null +++ b/yarn-project/circuit-types/src/prover_coordination/index.ts @@ -0,0 +1,2 @@ +export * from './epoch_proof_quote.js'; +export * from './epoch_proof_quote_payload.js'; diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts new file mode 100644 index 00000000000..89172315e9a --- /dev/null +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -0,0 +1,40 @@ +import { type DebugLogger, createDebugLogger } from '@aztec/aztec.js'; +import { makeRandomEpochProofQuote } from '@aztec/p2p'; + +import { beforeAll } from '@jest/globals'; + +import { + type ISnapshotManager, + type SubsystemsContext, + addAccounts, + createSnapshotManager, +} from '../fixtures/snapshot_manager.js'; + +// Tests simple block building with a sequencer that does not upload proofs to L1, +// and then follows with a prover node run (with real proofs disabled, but +// still simulating all circuits via a prover-client), in order to test +// the coordination through L1 between the sequencer and the prover node. +describe('e2e_prover_node', () => { + let ctx: SubsystemsContext; + + let logger: DebugLogger; + let snapshotManager: ISnapshotManager; + + beforeAll(async () => { + logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); + snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); + + await snapshotManager.snapshot('setup', addAccounts(2, logger)); + + ctx = await snapshotManager.setup(); + }); + + it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { + const { quote } = makeRandomEpochProofQuote(); + + await ctx.proverNode.sendEpochProofQuote(quote); + const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); + expect(receivedQuotes.length).toBe(1); + expect(receivedQuotes[0]).toEqual(quote); + }); +}); diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index e36054fd7fd..f07c0fe8ed6 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -8,6 +8,7 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; import { P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; +import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; @@ -19,6 +20,7 @@ export * from './p2p_client.js'; export const createP2PClient = async ( _config: P2PConfig & DataStoreConfig, attestationsPool: AttestationPool, + epochProofQuotePool: EpochProofQuotePool, l2BlockSource: L2BlockSource, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, @@ -52,7 +54,15 @@ export const createP2PClient = async ( } else { p2pService = new DummyP2PService(); } - return new P2PClient(store, l2BlockSource, txPool, attestationsPool, p2pService, config.keepProvenTxsInPoolFor); + return new P2PClient( + store, + l2BlockSource, + txPool, + attestationsPool, + epochProofQuotePool, + p2pService, + config.keepProvenTxsInPoolFor, + ); }; async function configureP2PClientAddresses(_config: P2PConfig & DataStoreConfig): Promise { diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 196a0ee45b7..98164007f05 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -6,7 +6,7 @@ import { openTmpStore } from '@aztec/kv-store/utils'; import { expect, jest } from '@jest/globals'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; -import { type P2PService } from '../index.js'; +import { type EpochProofQuotePool, type P2PService } from '../index.js'; import { type TxPool } from '../tx_pool/index.js'; import { MockBlockSource } from './mocks.js'; import { P2PClient } from './p2p_client.js'; @@ -21,6 +21,7 @@ type Mockify = { describe('In-Memory P2P Client', () => { let txPool: Mockify; let attestationPool: Mockify; + let epochProofQuotePool: Mockify; let blockSource: MockBlockSource; let p2pService: Mockify; let kvStore: AztecKVStore; @@ -55,10 +56,15 @@ describe('In-Memory P2P Client', () => { getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }; + epochProofQuotePool = { + addQuote: jest.fn(), + getQuotes: jest.fn().mockReturnValue([]), + }; + blockSource = new MockBlockSource(); kvStore = openTmpStore(); - client = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 0); + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); }); const advanceToProvenBlock = async (getProvenBlockNumber: number) => { @@ -72,13 +78,13 @@ describe('In-Memory P2P Client', () => { }; it('can start & stop', async () => { - expect(await client.isReady()).toEqual(false); + expect(client.isReady()).toEqual(false); await client.start(); - expect(await client.isReady()).toEqual(true); + expect(client.isReady()).toEqual(true); await client.stop(); - expect(await client.isReady()).toEqual(false); + expect(client.isReady()).toEqual(false); }); it('adds txs to pool', async () => { @@ -121,7 +127,7 @@ describe('In-Memory P2P Client', () => { await client.start(); await client.stop(); - const client2 = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 0); + const client2 = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); expect(client2.getSyncedLatestBlockNum()).toEqual(client.getSyncedLatestBlockNum()); }); @@ -136,7 +142,7 @@ describe('In-Memory P2P Client', () => { }); it('deletes txs after waiting the set number of blocks', async () => { - client = new P2PClient(kvStore, blockSource, txPool, attestationPool, p2pService, 10); + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 10); blockSource.setProvenBlockNumber(0); await client.start(); expect(txPool.deleteTxs).not.toHaveBeenCalled(); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 63c5b453f0a..6c6c1f6bdb5 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -1,6 +1,7 @@ import { type BlockAttestation, type BlockProposal, + type EpochProofQuote, type L2Block, L2BlockDownloader, type L2BlockSource, @@ -15,6 +16,7 @@ import { type ENR } from '@chainsafe/enr'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; import { getP2PConfigEnvVars } from '../config.js'; +import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { TX_REQ_PROTOCOL } from '../service/reqresp/interface.js'; import type { P2PService } from '../service/service.js'; import { type TxPool } from '../tx_pool/index.js'; @@ -62,6 +64,21 @@ export interface P2P { */ getAttestationsForSlot(slot: bigint): Promise; + /** + * Queries the EpochProofQuote pool for quotes for the given epoch + * + * @param epoch - the epoch to query + * @returns EpochProofQuotes + */ + getEpochProofQuotes(epoch: bigint): Promise; + + /** + * Broadcasts an EpochProofQuote to other peers. + * + * @param quote - the quote to broadcast + */ + broadcastEpochProofQuote(quote: EpochProofQuote): void; + /** * Registers a callback from the validator client that determines how to behave when * foreign block proposals are received @@ -134,7 +151,7 @@ export interface P2P { * Indicates if the p2p client is ready for transaction submission. * @returns A boolean flag indicating readiness. */ - isReady(): Promise; + isReady(): boolean; /** * Returns the current status of the p2p client. @@ -186,6 +203,7 @@ export class P2PClient implements P2P { private l2BlockSource: L2BlockSource, private txPool: TxPool, private attestationPool: AttestationPool, + private epochProofQuotePool: EpochProofQuotePool, private p2pService: P2PService, private keepProvenTxsFor: number, private log = createDebugLogger('aztec:p2p'), @@ -202,6 +220,22 @@ export class P2PClient implements P2P { this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block'); } + #assertIsReady() { + if (!this.isReady()) { + throw new Error('P2P client not ready'); + } + } + + getEpochProofQuotes(epoch: bigint): Promise { + return Promise.resolve(this.epochProofQuotePool.getQuotes(epoch)); + } + + broadcastEpochProofQuote(quote: EpochProofQuote): void { + this.#assertIsReady(); + this.epochProofQuotePool.addQuote(quote); + return this.p2pService.propagate(quote); + } + /** * Starts the P2P client. * @returns An empty promise signalling the synching process. @@ -363,10 +397,7 @@ export class P2PClient implements P2P { * @returns Empty promise. **/ public async sendTx(tx: Tx): Promise { - const ready = await this.isReady(); - if (!ready) { - throw new Error('P2P client not ready'); - } + this.#assertIsReady(); await this.txPool.addTxs([tx]); this.p2pService.propagate(tx); } @@ -391,10 +422,7 @@ export class P2PClient implements P2P { * @returns Empty promise. **/ public async deleteTxs(txHashes: TxHash[]): Promise { - const ready = await this.isReady(); - if (!ready) { - throw new Error('P2P client not ready'); - } + this.#assertIsReady(); await this.txPool.deleteTxs(txHashes); } @@ -403,7 +431,7 @@ export class P2PClient implements P2P { * @returns True if the P2P client is ready to receive txs. */ public isReady() { - return Promise.resolve(this.currentState === P2PClientState.RUNNING); + return this.currentState === P2PClientState.RUNNING; } /** diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts new file mode 100644 index 00000000000..6d9067c76d2 --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts @@ -0,0 +1,6 @@ +import { type EpochProofQuote } from '@aztec/circuit-types'; + +export interface EpochProofQuotePool { + addQuote(quote: EpochProofQuote): void; + getQuotes(epoch: bigint): EpochProofQuote[]; +} diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts new file mode 100644 index 00000000000..8073ff1866f --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/index.ts @@ -0,0 +1,3 @@ +export * from './epoch_proof_quote_pool.js'; +export * from './memory_epoch_proof_quote_pool.js'; +export * from './test_utils.js'; diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts new file mode 100644 index 00000000000..35c8611006f --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts @@ -0,0 +1,21 @@ +import { MemoryEpochProofQuotePool } from './memory_epoch_proof_quote_pool.js'; +import { makeRandomEpochProofQuote } from './test_utils.js'; + +describe('MemoryEpochProofQuotePool', () => { + let pool: MemoryEpochProofQuotePool; + + beforeEach(() => { + pool = new MemoryEpochProofQuotePool(); + }); + + it('should add/get quotes to/from pool', () => { + const { quote } = makeRandomEpochProofQuote(); + + pool.addQuote(quote); + + const quotes = pool.getQuotes(quote.payload.epochToProve); + + expect(quotes).toHaveLength(1); + expect(quotes[0]).toEqual(quote); + }); +}); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts new file mode 100644 index 00000000000..16b178c92fe --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts @@ -0,0 +1,18 @@ +import { type EpochProofQuote } from '@aztec/circuit-types'; + +export class MemoryEpochProofQuotePool { + private quotes: Map; + constructor() { + this.quotes = new Map(); + } + addQuote(quote: EpochProofQuote) { + const epoch = quote.payload.epochToProve; + if (!this.quotes.has(epoch)) { + this.quotes.set(epoch, []); + } + this.quotes.get(epoch)!.push(quote); + } + getQuotes(epoch: bigint): EpochProofQuote[] { + return this.quotes.get(epoch) || []; + } +} diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts new file mode 100644 index 00000000000..a12e10d85fa --- /dev/null +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts @@ -0,0 +1,25 @@ +import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types'; +import { EthAddress } from '@aztec/circuits.js'; +import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto'; + +export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { + return EpochProofQuotePayload.fromFields({ + basisPointFee: randomInt(10000), + bondAmount: 1000000000000000000n, + epochToProve: randomBigInt(1000000n), + rollupAddress: EthAddress.random(), + validUntilSlot: randomBigInt(1000000n), + }); +} + +export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): { + quote: EpochProofQuote; + signer: Secp256k1Signer; +} { + const signer = Secp256k1Signer.random(); + + return { + quote: EpochProofQuote.new(payload ?? makeRandomEpochProofQuotePayload(), signer), + signer, + }; +} diff --git a/yarn-project/p2p/src/index.ts b/yarn-project/p2p/src/index.ts index e69651a7904..4a5c64fda7e 100644 --- a/yarn-project/p2p/src/index.ts +++ b/yarn-project/p2p/src/index.ts @@ -1,7 +1,8 @@ +export * from './attestation_pool/index.js'; +export * from './bootstrap/bootstrap.js'; export * from './client/index.js'; export * from './config.js'; -export * from './tx_pool/index.js'; -export * from './attestation_pool/index.js'; +export * from './epoch_proof_quote_pool/index.js'; export * from './service/index.js'; -export * from './bootstrap/bootstrap.js'; +export * from './tx_pool/index.js'; export * from './tx_validator/index.js'; diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts index b52433a9b7b..08c028a535f 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts @@ -17,6 +17,7 @@ import { createP2PClient } from '../../client/index.js'; import { MockBlockSource } from '../../client/mocks.js'; import { type P2PClient } from '../../client/p2p_client.js'; import { type P2PConfig, getP2PDefaultConfig } from '../../config.js'; +import { type EpochProofQuotePool } from '../../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { AlwaysFalseCircuitVerifier, AlwaysTrueCircuitVerifier } from '../../mocks/index.js'; import { type TxPool } from '../../tx_pool/index.js'; import { convertToMultiaddr } from '../../util.js'; @@ -47,6 +48,7 @@ const NUMBER_OF_PEERS = 2; describe('Req Resp p2p client integration', () => { let txPool: Mockify; let attestationPool: Mockify; + let epochProofQuotePool: Mockify; let blockSource: MockBlockSource; let kvStore: AztecKVStore; let worldStateSynchronizer: WorldStateSynchronizer; @@ -134,6 +136,11 @@ describe('Req Resp p2p client integration', () => { getAttestationsForSlot: jest.fn().mockReturnValue(undefined), }; + epochProofQuotePool = { + addQuote: jest.fn(), + getQuotes: jest.fn().mockReturnValue([]), + }; + blockSource = new MockBlockSource(); proofVerifier = alwaysTrueVerifier ? new AlwaysTrueCircuitVerifier() : new AlwaysFalseCircuitVerifier(); kvStore = openTmpStore(); @@ -144,6 +151,7 @@ describe('Req Resp p2p client integration', () => { const client = await createP2PClient( config, attestationPool as unknown as AttestationPool, + epochProofQuotePool as unknown as EpochProofQuotePool, blockSource, proofVerifier, worldStateSynchronizer, diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 02b3a1b4448..42c30341512 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -1,10 +1,11 @@ -import { - type L1ToL2MessageSource, - type L2BlockSource, - type MerkleTreeOperations, - type ProverClient, - type TxProvider, - type WorldStateSynchronizer, +import type { + EpochProofQuote, + L1ToL2MessageSource, + L2BlockSource, + MerkleTreeOperations, + ProverClient, + TxProvider, + WorldStateSynchronizer, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; @@ -125,6 +126,10 @@ export class ProverNode { } } + public sendEpochProofQuote(quote: EpochProofQuote): Promise { + return this.txProvider.addEpochProofQuote(quote); + } + /** * Creates a proof for a block range. Returns once the proof has been submitted to L1. */ diff --git a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts index 90797602453..8e5dc75db18 100644 --- a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts +++ b/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts @@ -1,4 +1,4 @@ -import { type AztecNode, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; +import { type AztecNode, type EpochProofQuote, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; /** Implements TxProvider by querying an Aztec node for the txs. */ export class AztecNodeTxProvider implements TxProvider { @@ -7,4 +7,8 @@ export class AztecNodeTxProvider implements TxProvider { getTxByHash(txHash: TxHash): Promise { return this.node.getTxByHash(txHash); } + + addEpochProofQuote(quote: EpochProofQuote): Promise { + return this.node.addEpochProofQuote(quote); + } } From 4c283da088ac807a82c478c6959b1e055baf7b2b Mon Sep 17 00:00:00 2001 From: Mitch Date: Mon, 23 Sep 2024 15:07:04 -0400 Subject: [PATCH 21/43] rename TxProvider to ProverCoordination --- .../aztec-node/src/aztec-node/server.ts | 12 +- .../aztec/src/cli/cmds/start_prover_node.ts | 2 +- .../circuit-types/src/interfaces/index.ts | 14 +- ...{tx-provider.ts => prover-coordination.ts} | 4 +- .../src/e2e_prover/e2e_prover_test.ts | 2 +- .../end-to-end/src/e2e_prover_node.test.ts | 2 +- .../src/fixtures/snapshot_manager.ts | 2 +- yarn-project/foundation/src/config/env_var.ts | 214 +++++++++--------- yarn-project/p2p/src/client/index.ts | 13 +- .../reqresp/p2p_client.integration.test.ts | 13 +- yarn-project/prover-node/src/config.ts | 10 +- yarn-project/prover-node/src/factory.ts | 8 +- .../prover-node/src/job/block-proving-job.ts | 6 +- .../aztec-node-prover-coordination.ts} | 6 +- .../src/prover-coordination/config.ts | 17 ++ .../src/prover-coordination/factory.ts | 13 ++ .../index.ts | 4 +- .../prover-node/src/prover-node.test.ts | 6 +- yarn-project/prover-node/src/prover-node.ts | 8 +- .../prover-node/src/tx-provider/config.ts | 17 -- .../prover-node/src/tx-provider/factory.ts | 13 -- 21 files changed, 190 insertions(+), 196 deletions(-) rename yarn-project/circuit-types/src/interfaces/{tx-provider.ts => prover-coordination.ts} (82%) rename yarn-project/prover-node/src/{tx-provider/aztec-node-tx-provider.ts => prover-coordination/aztec-node-prover-coordination.ts} (52%) create mode 100644 yarn-project/prover-node/src/prover-coordination/config.ts create mode 100644 yarn-project/prover-node/src/prover-coordination/factory.ts rename yarn-project/prover-node/src/{tx-provider => prover-coordination}/index.ts (52%) delete mode 100644 yarn-project/prover-node/src/tx-provider/config.ts delete mode 100644 yarn-project/prover-node/src/tx-provider/factory.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index b6e5d3ddc40..83860d56b89 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -59,8 +59,6 @@ import { AggregateTxValidator, DataTxValidator, DoubleSpendTxValidator, - InMemoryAttestationPool, - MemoryEpochProofQuotePool, MetadataTxValidator, type P2P, TxProofValidator, @@ -161,15 +159,7 @@ export class AztecNodeService implements AztecNode { const proofVerifier = config.realProofs ? await BBCircuitVerifier.new(config) : new TestCircuitVerifier(); // create the tx pool and the p2p client, which will need the l2 block source - const p2pClient = await createP2PClient( - config, - new InMemoryAttestationPool(), - new MemoryEpochProofQuotePool(), - archiver, - proofVerifier, - worldStateSynchronizer, - telemetry, - ); + const p2pClient = await createP2PClient(config, archiver, proofVerifier, worldStateSynchronizer, telemetry); // start both and wait for them to sync from the block source await Promise.all([p2pClient.start(), worldStateSynchronizer.start()]); diff --git a/yarn-project/aztec/src/cli/cmds/start_prover_node.ts b/yarn-project/aztec/src/cli/cmds/start_prover_node.ts index e1e95a9a1f8..ca054a01ec4 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover_node.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover_node.ts @@ -63,7 +63,7 @@ export const startProverNode = async ( // Load l1 contract addresses from aztec node if not set. const isRollupAddressSet = proverConfig.l1Contracts?.rollupAddress && !proverConfig.l1Contracts.rollupAddress.isZero(); - const nodeUrl = proverConfig.nodeUrl ?? proverConfig.txProviderNodeUrl; + const nodeUrl = proverConfig.nodeUrl ?? proverConfig.proverCoordinationNodeUrl; if (nodeUrl && !isRollupAddressSet) { userLog(`Loading L1 contract addresses from aztec node at ${nodeUrl}`); proverConfig.l1Contracts = await createAztecNodeClient(nodeUrl).getL1ContractAddresses(); diff --git a/yarn-project/circuit-types/src/interfaces/index.ts b/yarn-project/circuit-types/src/interfaces/index.ts index 1ef99ee52db..63a79b5a4be 100644 --- a/yarn-project/circuit-types/src/interfaces/index.ts +++ b/yarn-project/circuit-types/src/interfaces/index.ts @@ -1,14 +1,14 @@ export * from './aztec-node.js'; -export * from './l2_block_number.js'; -export * from './pxe.js'; -export * from './sync-status.js'; +export * from './block-prover.js'; export * from './configs.js'; +export * from './l2_block_number.js'; +export * from './merkle_tree_operations.js'; export * from './nullifier_tree.js'; +export * from './private_kernel_prover.js'; export * from './prover-client.js'; +export * from './prover-coordination.js'; export * from './proving-job.js'; -export * from './block-prover.js'; +export * from './pxe.js'; export * from './server_circuit_prover.js'; -export * from './private_kernel_prover.js'; -export * from './tx-provider.js'; -export * from './merkle_tree_operations.js'; +export * from './sync-status.js'; export * from './world_state.js'; diff --git a/yarn-project/circuit-types/src/interfaces/tx-provider.ts b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts similarity index 82% rename from yarn-project/circuit-types/src/interfaces/tx-provider.ts rename to yarn-project/circuit-types/src/interfaces/prover-coordination.ts index 60b7f59e9b9..01918a34d39 100644 --- a/yarn-project/circuit-types/src/interfaces/tx-provider.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts @@ -2,8 +2,8 @@ import { type EpochProofQuote } from '../prover_coordination/index.js'; import { type Tx } from '../tx/tx.js'; import { type TxHash } from '../tx/tx_hash.js'; -/** Provider for transaction objects given their hash. */ -export interface TxProvider { +/** Provides basic operations for ProverNodes to interact with other nodes in the network. */ +export interface ProverCoordination { /** * Returns a transaction given its hash if available. * @param txHash - The hash of the transaction, used as an ID. 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 89a833ff216..b17ff2a8bbe 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 @@ -242,7 +242,7 @@ export class FullProverTest { this.logger.verbose('Starting fully proven prover node'); const proverConfig: ProverNodeConfig = { ...this.context.aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId: new Fr(81), realProofs: this.realProofs, diff --git a/yarn-project/end-to-end/src/e2e_prover_node.test.ts b/yarn-project/end-to-end/src/e2e_prover_node.test.ts index efab32e13fd..d2cff793446 100644 --- a/yarn-project/end-to-end/src/e2e_prover_node.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover_node.test.ts @@ -115,7 +115,7 @@ describe('e2e_prover_node', () => { // snapshot manager does not include events nor txs, so a new archiver would not "see" old blocks. const proverConfig: ProverNodeConfig = { ...ctx.aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId, proverNodeMaxPendingJobs: 100, 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 bb1af6a9da5..a758372ea7c 100644 --- a/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts +++ b/yarn-project/end-to-end/src/fixtures/snapshot_manager.ts @@ -244,7 +244,7 @@ export async function createAndSyncProverNode( // Prover node config is for simulated proofs const proverConfig: ProverNodeConfig = { ...aztecNodeConfig, - txProviderNodeUrl: undefined, + proverCoordinationNodeUrl: undefined, dataDirectory: undefined, proverId: new Fr(42), realProofs: false, diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 5183e2847ed..efbf60a5d4e 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -1,132 +1,132 @@ export type EnvVar = - | 'AZTEC_PORT' + | 'ACVM_BINARY_PATH' + | 'ACVM_WORKING_DIRECTORY' + | 'API_KEY' + | 'API_PREFIX' + | 'ARCHIVER_MAX_LOGS' + | 'ARCHIVER_POLLING_INTERVAL_MS' + | 'ARCHIVER_URL' + | 'ARCHIVER_VIEM_POLLING_INTERVAL_MS' | 'ASSUME_PROVEN_THROUGH_BLOCK_NUMBER' - | 'TEST_ACCOUNTS' + | 'AZTEC_NODE_URL' + | 'AZTEC_PORT' + | 'BB_BINARY_PATH' + | 'BB_SKIP_CLEANUP' + | 'BB_WORKING_DIRECTORY' + | 'BOOTSTRAP_NODES' + | 'BOT_DA_GAS_LIMIT' + | 'BOT_FEE_PAYMENT_METHOD' + | 'BOT_FLUSH_SETUP_TRANSACTIONS' + | 'BOT_FOLLOW_CHAIN' + | 'BOT_L2_GAS_LIMIT' + | 'BOT_MAX_PENDING_TXS' + | 'BOT_NO_START' + | 'BOT_NO_WAIT_FOR_TRANSFERS' + | 'BOT_PRIVATE_KEY' + | 'BOT_PRIVATE_TRANSFERS_PER_TX' + | 'BOT_PUBLIC_TRANSFERS_PER_TX' + | 'BOT_PXE_URL' + | 'BOT_RECIPIENT_ENCRYPTION_SECRET' + | 'BOT_SKIP_PUBLIC_SIMULATION' + | 'BOT_TOKEN_CONTRACT' + | 'BOT_TOKEN_SALT' + | 'BOT_TX_INTERVAL_SECONDS' + | 'BOT_TX_MINED_WAIT_SECONDS' + | 'COINBASE' + | 'DATA_DIRECTORY' + | 'DEBUG' + | 'DEPLOY_AZTEC_CONTRACTS_SALT' + | 'DEPLOY_AZTEC_CONTRACTS' | 'ENABLE_GAS' - | 'API_PREFIX' + | 'ENFORCE_FEES' | 'ETHEREUM_HOST' - | 'L1_CHAIN_ID' - | 'MNEMONIC' - | 'ROLLUP_CONTRACT_ADDRESS' - | 'REGISTRY_CONTRACT_ADDRESS' - | 'INBOX_CONTRACT_ADDRESS' - | 'OUTBOX_CONTRACT_ADDRESS' | 'FEE_JUICE_CONTRACT_ADDRESS' | 'FEE_JUICE_PORTAL_CONTRACT_ADDRESS' - | 'ARCHIVER_URL' - | 'DEPLOY_AZTEC_CONTRACTS' - | 'DEPLOY_AZTEC_CONTRACTS_SALT' + | 'FEE_RECIPIENT' + | 'INBOX_CONTRACT_ADDRESS' + | 'L1_CHAIN_ID' | 'L1_PRIVATE_KEY' | 'L2_QUEUE_SIZE' - | 'WS_BLOCK_CHECK_INTERVAL_MS' - | 'P2P_ENABLED' + | 'LOG_JSON' + | 'LOG_LEVEL' + | 'MNEMONIC' + | 'NETWORK_NAME' + | 'NETWORK' + | 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' + | 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' + | 'OTEL_SERVICE_NAME' + | 'OUTBOX_CONTRACT_ADDRESS' | 'P2P_BLOCK_CHECK_INTERVAL_MS' - | 'P2P_PEER_CHECK_INTERVAL_MS' - | 'P2P_L2_QUEUE_SIZE' - | 'TCP_LISTEN_ADDR' - | 'UDP_LISTEN_ADDR' - | 'P2P_TCP_ANNOUNCE_ADDR' - | 'P2P_UDP_ANNOUNCE_ADDR' - | 'PEER_ID_PRIVATE_KEY' - | 'BOOTSTRAP_NODES' - | 'P2P_TX_PROTOCOL' - | 'P2P_MIN_PEERS' - | 'P2P_MAX_PEERS' - | 'DATA_DIRECTORY' - | 'TX_GOSSIP_VERSION' - | 'P2P_QUERY_FOR_IP' - | 'P2P_TX_POOL_KEEP_PROVEN_FOR' - | 'P2P_GOSSIPSUB_INTERVAL_MS' + | 'P2P_ENABLED' | 'P2P_GOSSIPSUB_D' - | 'P2P_GOSSIPSUB_DLO' | 'P2P_GOSSIPSUB_DHI' - | 'P2P_GOSSIPSUB_MCACHE_LENGTH' + | 'P2P_GOSSIPSUB_DLO' + | 'P2P_GOSSIPSUB_INTERVAL_MS' | 'P2P_GOSSIPSUB_MCACHE_GOSSIP' - | 'P2P_SEVERE_PEER_PENALTY_BLOCK_LENGTH' - | 'P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS' - | 'P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS' - | 'P2P_GOSSIPSUB_TX_TOPIC_WEIGHT' - | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT' + | 'P2P_GOSSIPSUB_MCACHE_LENGTH' | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_DECAY' + | 'P2P_GOSSIPSUB_TX_INVALID_MESSAGE_DELIVERIES_WEIGHT' + | 'P2P_GOSSIPSUB_TX_TOPIC_WEIGHT' + | 'P2P_L2_QUEUE_SIZE' + | 'P2P_MAX_PEERS' + | 'P2P_MIN_PEERS' + | 'P2P_PEER_CHECK_INTERVAL_MS' | 'P2P_PEER_PENALTY_VALUES' - | 'TELEMETRY' - | 'OTEL_SERVICE_NAME' - | 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT' - | 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - | 'NETWORK_NAME' - | 'NETWORK' - | 'API_KEY' - | 'AZTEC_NODE_URL' - | 'ARCHIVER_POLLING_INTERVAL_MS' - | 'ARCHIVER_VIEM_POLLING_INTERVAL_MS' - | 'ARCHIVER_MAX_LOGS' - | 'SEQ_TX_POLLING_INTERVAL_MS' - | 'SEQ_MAX_TX_PER_BLOCK' - | 'SEQ_MIN_TX_PER_BLOCK' - | 'SEQ_MIN_SECONDS_BETWEEN_BLOCKS' - | 'SEQ_MAX_SECONDS_BETWEEN_BLOCKS' - | 'COINBASE' - | 'FEE_RECIPIENT' - | 'ACVM_WORKING_DIRECTORY' - | 'ACVM_BINARY_PATH' - | 'SEQ_ALLOWED_SETUP_FN' - | 'SEQ_ALLOWED_TEARDOWN_FN' - | 'SEQ_MAX_BLOCK_SIZE_IN_BYTES' - | 'ENFORCE_FEES' - | 'SEQ_PUBLISHER_PRIVATE_KEY' - | 'SEQ_REQUIRED_CONFIRMATIONS' - | 'SEQ_PUBLISH_RETRY_INTERVAL_MS' - | 'VERSION' - | 'SEQ_DISABLED' - | 'PROVER_DISABLED' - | 'PROVER_REAL_PROOFS' + | 'P2P_QUERY_FOR_IP' + | 'P2P_REQRESP_INDIVIDUAL_REQUEST_TIMEOUT_MS' + | 'P2P_REQRESP_OVERALL_REQUEST_TIMEOUT_MS' + | 'P2P_SEVERE_PEER_PENALTY_BLOCK_LENGTH' + | 'P2P_TCP_ANNOUNCE_ADDR' + | 'P2P_TX_POOL_KEEP_PROVEN_FOR' + | 'P2P_TX_PROTOCOL' + | 'P2P_UDP_ANNOUNCE_ADDR' + | 'PEER_ID_PRIVATE_KEY' + | 'PROOF_VERIFIER_L1_START_BLOCK' + | 'PROOF_VERIFIER_POLL_INTERVAL_MS' + | 'PROVER_AGENT_CONCURRENCY' | 'PROVER_AGENT_ENABLED' | 'PROVER_AGENT_POLL_INTERVAL_MS' - | 'PROVER_AGENT_CONCURRENCY' - | 'PROVER_JOB_TIMEOUT_MS' - | 'PROVER_JOB_POLL_INTERVAL_MS' + | 'PROVER_COORDINATION_NODE_URL' + | 'PROVER_DISABLED' | 'PROVER_ID' - | 'WS_L2_BLOCK_QUEUE_SIZE' - | 'WS_PROVEN_BLOCKS_ONLY' + | 'PROVER_JOB_POLL_INTERVAL_MS' + | 'PROVER_JOB_TIMEOUT_MS' + | 'PROVER_NODE_DISABLE_AUTOMATIC_PROVING' + | 'PROVER_NODE_MAX_PENDING_JOBS' | 'PROVER_PUBLISH_RETRY_INTERVAL_MS' | 'PROVER_PUBLISHER_PRIVATE_KEY' + | 'PROVER_REAL_PROOFS' | 'PROVER_REQUIRED_CONFIRMATIONS' | 'PROVER_TEST_DELAY_MS' - | 'TX_PROVIDER_NODE_URL' - | 'TXE_PORT' - | 'LOG_JSON' - | 'BOT_PXE_URL' - | 'BOT_PRIVATE_KEY' - | 'BOT_RECIPIENT_ENCRYPTION_SECRET' - | 'BOT_TOKEN_SALT' - | 'BOT_TX_INTERVAL_SECONDS' - | 'BOT_PRIVATE_TRANSFERS_PER_TX' - | 'BOT_PUBLIC_TRANSFERS_PER_TX' - | 'BOT_FEE_PAYMENT_METHOD' - | 'BOT_NO_START' - | 'BOT_TX_MINED_WAIT_SECONDS' - | 'BOT_NO_WAIT_FOR_TRANSFERS' - | 'BOT_MAX_PENDING_TXS' - | 'BOT_SKIP_PUBLIC_SIMULATION' - | 'BOT_L2_GAS_LIMIT' - | 'BOT_DA_GAS_LIMIT' | 'PXE_BLOCK_POLLING_INTERVAL_MS' - | 'PXE_L2_STARTING_BLOCK' | 'PXE_DATA_DIRECTORY' - | 'BB_BINARY_PATH' - | 'BB_WORKING_DIRECTORY' - | 'BB_SKIP_CLEANUP' + | 'PXE_L2_STARTING_BLOCK' | 'PXE_PROVER_ENABLED' - | 'BOT_FOLLOW_CHAIN' - | 'BOT_FLUSH_SETUP_TRANSACTIONS' - | 'BOT_TOKEN_CONTRACT' - | 'VALIDATOR_PRIVATE_KEY' - | 'VALIDATOR_DISABLED' - | 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS' + | 'REGISTRY_CONTRACT_ADDRESS' + | 'ROLLUP_CONTRACT_ADDRESS' + | 'SEQ_ALLOWED_SETUP_FN' + | 'SEQ_ALLOWED_TEARDOWN_FN' + | 'SEQ_DISABLED' + | 'SEQ_MAX_BLOCK_SIZE_IN_BYTES' + | 'SEQ_MAX_SECONDS_BETWEEN_BLOCKS' + | 'SEQ_MAX_TX_PER_BLOCK' + | 'SEQ_MIN_SECONDS_BETWEEN_BLOCKS' + | 'SEQ_MIN_TX_PER_BLOCK' + | 'SEQ_PUBLISH_RETRY_INTERVAL_MS' + | 'SEQ_PUBLISHER_PRIVATE_KEY' + | 'SEQ_REQUIRED_CONFIRMATIONS' + | 'SEQ_TX_POLLING_INTERVAL_MS' + | 'TCP_LISTEN_ADDR' + | 'TELEMETRY' + | 'TEST_ACCOUNTS' + | 'TX_GOSSIP_VERSION' + | 'TXE_PORT' + | 'UDP_LISTEN_ADDR' | 'VALIDATOR_ATTESTATIONS_POOLING_INTERVAL_MS' - | 'PROVER_NODE_DISABLE_AUTOMATIC_PROVING' - | 'PROVER_NODE_MAX_PENDING_JOBS' - | 'PROOF_VERIFIER_POLL_INTERVAL_MS' - | 'PROOF_VERIFIER_L1_START_BLOCK' - | 'LOG_LEVEL' - | 'DEBUG'; + | 'VALIDATOR_ATTESTATIONS_WAIT_TIMEOUT_MS' + | 'VALIDATOR_DISABLED' + | 'VALIDATOR_PRIVATE_KEY' + | 'VERSION' + | 'WS_BLOCK_CHECK_INTERVAL_MS' + | 'WS_L2_BLOCK_QUEUE_SIZE' + | 'WS_PROVEN_BLOCKS_ONLY'; diff --git a/yarn-project/p2p/src/client/index.ts b/yarn-project/p2p/src/client/index.ts index f07c0fe8ed6..aaafccba9f1 100644 --- a/yarn-project/p2p/src/client/index.ts +++ b/yarn-project/p2p/src/client/index.ts @@ -6,9 +6,11 @@ import { type TelemetryClient } from '@aztec/telemetry-client'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { type AttestationPool } from '../attestation_pool/attestation_pool.js'; +import { InMemoryAttestationPool } from '../attestation_pool/memory_attestation_pool.js'; import { P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; import { type EpochProofQuotePool } from '../epoch_proof_quote_pool/epoch_proof_quote_pool.js'; +import { MemoryEpochProofQuotePool } from '../epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js'; import { DiscV5Service } from '../service/discV5_service.js'; import { DummyP2PService } from '../service/dummy_service.js'; import { LibP2PService, createLibP2PPeerId } from '../service/index.js'; @@ -19,17 +21,22 @@ export * from './p2p_client.js'; export const createP2PClient = async ( _config: P2PConfig & DataStoreConfig, - attestationsPool: AttestationPool, - epochProofQuotePool: EpochProofQuotePool, l2BlockSource: L2BlockSource, proofVerifier: ClientProtocolCircuitVerifier, worldStateSynchronizer: WorldStateSynchronizer, telemetry: TelemetryClient = new NoopTelemetryClient(), - deps: { txPool?: TxPool; store?: AztecKVStore } = {}, + deps: { + txPool?: TxPool; + store?: AztecKVStore; + attestationsPool?: AttestationPool; + epochProofQuotePool?: EpochProofQuotePool; + } = {}, ) => { let config = { ..._config }; const store = deps.store ?? (await createStore('p2p', config, createDebugLogger('aztec:p2p:lmdb'))); const txPool = deps.txPool ?? new AztecKVTxPool(store, telemetry); + const attestationsPool = deps.attestationsPool ?? new InMemoryAttestationPool(); + const epochProofQuotePool = deps.epochProofQuotePool ?? new MemoryEpochProofQuotePool(); let p2pService; diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts index 08c028a535f..ebc3344680d 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts @@ -146,18 +146,11 @@ describe('Req Resp p2p client integration', () => { kvStore = openTmpStore(); const deps = { txPool: txPool as unknown as TxPool, + attestationPool: attestationPool as unknown as AttestationPool, + epochProofQuotePool: epochProofQuotePool as unknown as EpochProofQuotePool, store: kvStore, }; - const client = await createP2PClient( - config, - attestationPool as unknown as AttestationPool, - epochProofQuotePool as unknown as EpochProofQuotePool, - blockSource, - proofVerifier, - worldStateSynchronizer, - undefined, - deps, - ); + const client = await createP2PClient(config, blockSource, proofVerifier, worldStateSynchronizer, undefined, deps); await client.start(); clients.push(client); diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts index d7229c80afe..7da91540f0c 100644 --- a/yarn-project/prover-node/src/config.ts +++ b/yarn-project/prover-node/src/config.ts @@ -16,14 +16,18 @@ import { } from '@aztec/sequencer-client'; import { type WorldStateConfig, getWorldStateConfigFromEnv, worldStateConfigMappings } from '@aztec/world-state'; -import { type TxProviderConfig, getTxProviderConfigFromEnv, txProviderConfigMappings } from './tx-provider/config.js'; +import { + type ProverCoordinationConfig, + getTxProviderConfigFromEnv, + proverCoordinationConfigMappings, +} from './prover-coordination/config.js'; export type ProverNodeConfig = ArchiverConfig & ProverClientConfig & WorldStateConfig & PublisherConfig & TxSenderConfig & - TxProviderConfig & { + ProverCoordinationConfig & { proverNodeDisableAutomaticProving?: boolean; proverNodeMaxPendingJobs?: number; }; @@ -49,7 +53,7 @@ export const proverNodeConfigMappings: ConfigMappingsType = { ...worldStateConfigMappings, ...getPublisherConfigMappings('PROVER'), ...getTxSenderConfigMappings('PROVER'), - ...txProviderConfigMappings, + ...proverCoordinationConfigMappings, ...specificProverNodeConfigMappings, }; diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index 7e9c31e8cbf..088a7cb32ac 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -9,9 +9,9 @@ import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { createWorldStateSynchronizer } from '@aztec/world-state'; import { type ProverNodeConfig } from './config.js'; +import { AztecNodeProverCoordination } from './prover-coordination/aztec-node-prover-coordination.js'; +import { createProverCoordination } from './prover-coordination/factory.js'; import { ProverNode } from './prover-node.js'; -import { AztecNodeTxProvider } from './tx-provider/aztec-node-tx-provider.js'; -import { createTxProvider } from './tx-provider/factory.js'; /** Creates a new prover node given a config. */ export async function createProverNode( @@ -40,8 +40,8 @@ export async function createProverNode( const publisher = new L1Publisher(config, telemetry); const txProvider = deps.aztecNodeTxProvider - ? new AztecNodeTxProvider(deps.aztecNodeTxProvider) - : createTxProvider(config); + ? new AztecNodeProverCoordination(deps.aztecNodeTxProvider) + : createProverCoordination(config); return new ProverNode( prover!, diff --git a/yarn-project/prover-node/src/job/block-proving-job.ts b/yarn-project/prover-node/src/job/block-proving-job.ts index 4c11f152210..c0e467696ed 100644 --- a/yarn-project/prover-node/src/job/block-proving-job.ts +++ b/yarn-project/prover-node/src/job/block-proving-job.ts @@ -6,9 +6,9 @@ import { type L2BlockSource, PROVING_STATUS, type ProcessedTx, + type ProverCoordination, type Tx, type TxHash, - type TxProvider, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; @@ -35,7 +35,7 @@ export class BlockProvingJob { private publisher: L1Publisher, private l2BlockSource: L2BlockSource, private l1ToL2MessageSource: L1ToL2MessageSource, - private txProvider: TxProvider, + private coordination: ProverCoordination, private metrics: ProverNodeMetrics, private cleanUp: (job: BlockProvingJob) => Promise = () => Promise.resolve(), ) { @@ -142,7 +142,7 @@ export class BlockProvingJob { private async getTxs(txHashes: TxHash[]): Promise { const txs = await Promise.all( - txHashes.map(txHash => this.txProvider.getTxByHash(txHash).then(tx => [txHash, tx] as const)), + txHashes.map(txHash => this.coordination.getTxByHash(txHash).then(tx => [txHash, tx] as const)), ); const notFound = txs.filter(([_, tx]) => !tx); if (notFound.length) { diff --git a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts b/yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts similarity index 52% rename from yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts rename to yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts index 8e5dc75db18..3152cd2ebf8 100644 --- a/yarn-project/prover-node/src/tx-provider/aztec-node-tx-provider.ts +++ b/yarn-project/prover-node/src/prover-coordination/aztec-node-prover-coordination.ts @@ -1,7 +1,7 @@ -import { type AztecNode, type EpochProofQuote, type Tx, type TxHash, type TxProvider } from '@aztec/circuit-types'; +import type { AztecNode, EpochProofQuote, ProverCoordination, Tx, TxHash } from '@aztec/circuit-types'; -/** Implements TxProvider by querying an Aztec node for the txs. */ -export class AztecNodeTxProvider implements TxProvider { +/** Implements ProverCoordinator by wrapping an Aztec node */ +export class AztecNodeProverCoordination implements ProverCoordination { constructor(private node: AztecNode) {} getTxByHash(txHash: TxHash): Promise { diff --git a/yarn-project/prover-node/src/prover-coordination/config.ts b/yarn-project/prover-node/src/prover-coordination/config.ts new file mode 100644 index 00000000000..7940c803e96 --- /dev/null +++ b/yarn-project/prover-node/src/prover-coordination/config.ts @@ -0,0 +1,17 @@ +import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config'; + +export type ProverCoordinationConfig = { + proverCoordinationNodeUrl: string | undefined; +}; + +export const proverCoordinationConfigMappings: ConfigMappingsType = { + proverCoordinationNodeUrl: { + env: 'PROVER_COORDINATION_NODE_URL', + description: 'The URL of the tx provider node', + parseEnv: (val: string) => val, + }, +}; + +export function getTxProviderConfigFromEnv(): ProverCoordinationConfig { + return getConfigFromMappings(proverCoordinationConfigMappings); +} diff --git a/yarn-project/prover-node/src/prover-coordination/factory.ts b/yarn-project/prover-node/src/prover-coordination/factory.ts new file mode 100644 index 00000000000..71e5ba8a95e --- /dev/null +++ b/yarn-project/prover-node/src/prover-coordination/factory.ts @@ -0,0 +1,13 @@ +import { type ProverCoordination, createAztecNodeClient } from '@aztec/circuit-types'; + +import { AztecNodeProverCoordination } from './aztec-node-prover-coordination.js'; +import { type ProverCoordinationConfig } from './config.js'; + +export function createProverCoordination(config: ProverCoordinationConfig): ProverCoordination { + if (config.proverCoordinationNodeUrl) { + const node = createAztecNodeClient(config.proverCoordinationNodeUrl); + return new AztecNodeProverCoordination(node); + } else { + throw new Error(`Aztec Node URL for Tx Provider is not set.`); + } +} diff --git a/yarn-project/prover-node/src/tx-provider/index.ts b/yarn-project/prover-node/src/prover-coordination/index.ts similarity index 52% rename from yarn-project/prover-node/src/tx-provider/index.ts rename to yarn-project/prover-node/src/prover-coordination/index.ts index bac271dd877..7394c367754 100644 --- a/yarn-project/prover-node/src/tx-provider/index.ts +++ b/yarn-project/prover-node/src/prover-coordination/index.ts @@ -1,3 +1,3 @@ -export * from './aztec-node-tx-provider.js'; -export * from './factory.js'; +export * from './aztec-node-prover-coordination.js'; export * from './config.js'; +export * from './factory.js'; diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index e5cd74cfa32..3c9e5e6325d 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -3,7 +3,7 @@ import { type L2BlockSource, type MerkleTreeAdminOperations, type ProverClient, - type TxProvider, + type ProverCoordination, WorldStateRunningState, type WorldStateSynchronizer, } from '@aztec/circuit-types'; @@ -24,7 +24,7 @@ describe('prover-node', () => { let l1ToL2MessageSource: MockProxy; let contractDataSource: MockProxy; let worldState: MockProxy; - let txProvider: MockProxy; + let txProvider: MockProxy; let simulator: MockProxy; let proverNode: TestProverNode; @@ -43,7 +43,7 @@ describe('prover-node', () => { l1ToL2MessageSource = mock(); contractDataSource = mock(); worldState = mock(); - txProvider = mock(); + txProvider = mock(); simulator = mock(); const telemetryClient = new NoopTelemetryClient(); diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 42c30341512..952a32a0ca4 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -4,7 +4,7 @@ import type { L2BlockSource, MerkleTreeOperations, ProverClient, - TxProvider, + ProverCoordination, WorldStateSynchronizer, } from '@aztec/circuit-types'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -37,7 +37,7 @@ export class ProverNode { private l1ToL2MessageSource: L1ToL2MessageSource, private contractDataSource: ContractDataSource, private worldState: WorldStateSynchronizer, - private txProvider: TxProvider, + private coordination: ProverCoordination, private simulator: SimulationProvider, private telemetryClient: TelemetryClient, options: { pollingIntervalMs?: number; disableAutomaticProving?: boolean; maxPendingJobs?: number } = {}, @@ -127,7 +127,7 @@ export class ProverNode { } public sendEpochProofQuote(quote: EpochProofQuote): Promise { - return this.txProvider.addEpochProofQuote(quote); + return this.coordination.addEpochProofQuote(quote); } /** @@ -208,7 +208,7 @@ export class ProverNode { this.publisher, this.l2BlockSource, this.l1ToL2MessageSource, - this.txProvider, + this.coordination, this.metrics, cleanUp, ); diff --git a/yarn-project/prover-node/src/tx-provider/config.ts b/yarn-project/prover-node/src/tx-provider/config.ts deleted file mode 100644 index 5fc9ed9465d..00000000000 --- a/yarn-project/prover-node/src/tx-provider/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { type ConfigMappingsType, getConfigFromMappings } from '@aztec/foundation/config'; - -export type TxProviderConfig = { - txProviderNodeUrl: string | undefined; -}; - -export const txProviderConfigMappings: ConfigMappingsType = { - txProviderNodeUrl: { - env: 'TX_PROVIDER_NODE_URL', - description: 'The URL of the tx provider node', - parseEnv: (val: string) => val, - }, -}; - -export function getTxProviderConfigFromEnv(): TxProviderConfig { - return getConfigFromMappings(txProviderConfigMappings); -} diff --git a/yarn-project/prover-node/src/tx-provider/factory.ts b/yarn-project/prover-node/src/tx-provider/factory.ts deleted file mode 100644 index e17d13e00c0..00000000000 --- a/yarn-project/prover-node/src/tx-provider/factory.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type TxProvider, createAztecNodeClient } from '@aztec/circuit-types'; - -import { AztecNodeTxProvider } from './aztec-node-tx-provider.js'; -import { type TxProviderConfig } from './config.js'; - -export function createTxProvider(config: TxProviderConfig): TxProvider { - if (config.txProviderNodeUrl) { - const node = createAztecNodeClient(config.txProviderNodeUrl); - return new AztecNodeTxProvider(node); - } else { - throw new Error(`Aztec Node URL for Tx Provider is not set.`); - } -} From c6ccab92ebe4b059709b368e8c944bbd70d1eb6e Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 17 Sep 2024 15:55:34 -0400 Subject: [PATCH 22/43] define escrow contract interface implement mock escrow and include in rollup move signature lib --- .../src/core/libraries/DataStructures.sol | 2 ++ .../src/core/libraries/SignatureLib.sol | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 l1-contracts/src/core/libraries/SignatureLib.sol diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 13911e4d7c2..6e5ecb85e59 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -6,6 +6,8 @@ import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; +import {SignatureLib} from "./SignatureLib.sol"; + /** * @title Data Structures Library * @author Aztec Labs diff --git a/l1-contracts/src/core/libraries/SignatureLib.sol b/l1-contracts/src/core/libraries/SignatureLib.sol new file mode 100644 index 00000000000..af5cdf8eb30 --- /dev/null +++ b/l1-contracts/src/core/libraries/SignatureLib.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity ^0.8.13; + +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +library SignatureLib { + struct Signature { + bool isEmpty; + uint8 v; + bytes32 r; + bytes32 s; + } + + /** + * @notice Verified a signature, throws if the signature is invalid or empty + * + * @param _signature - The signature to verify + * @param _signer - The expected signer of the signature + * @param _digest - The digest that was signed + */ + function verify(Signature memory _signature, address _signer, bytes32 _digest) internal pure { + if (_signature.isEmpty) { + revert Errors.SignatureLib__CannotVerifyEmpty(); + } + address recovered = ecrecover(_digest, _signature.v, _signature.r, _signature.s); + if (_signer != recovered) { + revert Errors.SignatureLib__InvalidSignature(_signer, recovered); + } + } +} From ed522c83c57ab5a512ba034be1865611d4ccc3f5 Mon Sep 17 00:00:00 2001 From: Mitch Date: Tue, 17 Sep 2024 19:20:04 -0400 Subject: [PATCH 23/43] prune if needed before propose and prove. --- l1-contracts/src/core/Rollup.sol | 30 +++++++++++++++++++++++++++ l1-contracts/test/sparta/Sparta.t.sol | 6 ++++++ 2 files changed, 36 insertions(+) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 92e618fe69e..782a3910bce 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -720,6 +720,36 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + function _prune() internal { + uint256 pending = tips.pendingBlockNumber; + + // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. + // We can do because any new block proposed will overwrite a previous block in the block log, + // so no values should "survive". + // People must therefore read the chain using the pendingTip as a boundary. + tips.pendingBlockNumber = tips.provenBlockNumber; + + emit PrunedPending(tips.provenBlockNumber, pending); + } + + function _canPrune() internal view returns (bool) { + if (tips.pendingBlockNumber == tips.provenBlockNumber) { + return false; + } + + uint256 currentSlot = getCurrentSlot(); + uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); + uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; + + // TODO: #8608 adds a proof claim, which will allow us to prune the chain more aggressively. + // That is what will add a `CLAIM_DURATION` to the pruning logic. + if (currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION) { + return false; + } + + return true; + } + /** * @notice Get the epoch that should be proven * diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 1dfd393df44..76516ab4090 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -40,6 +40,12 @@ contract SpartaTest is DecoderBase { bool shouldRevert; } + struct StructToAvoidDeepStacks { + uint256 needed; + address proposer; + bool shouldRevert; + } + Registry internal registry; Inbox internal inbox; Outbox internal outbox; From 427b4e9d02e04fdfcb82075952084f9cbd23f41b Mon Sep 17 00:00:00 2001 From: Mitch Date: Wed, 18 Sep 2024 14:44:56 -0400 Subject: [PATCH 24/43] Update test to hit code path --- l1-contracts/test/Rollup.t.sol | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 90798ca24f7..ca1a4c60b48 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -341,7 +341,7 @@ contract RollupTest is DecoderBase { rollup.prune(); } - function testPrune() public setUpFor("mixed_block_1") { + function testPruneDuringPropose() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false); assertEq(inbox.inProgress(), 3, "Invalid in progress"); @@ -370,16 +370,12 @@ contract RollupTest is DecoderBase { assertNotEq(rootMixed, bytes32(0), "Invalid root"); assertNotEq(minHeightMixed, 0, "Invalid min height"); - rollup.prune(); - assertEq(inbox.inProgress(), 3, "Invalid in progress"); - assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - // @note We alter what slot is specified in the empty block! // This means that we keep the `empty_block_1` mostly as is, but replace the slot number // and timestamp as if it was created at a different point in time. This allow us to insert it // as if it was the first block, even after we had originally inserted the mixed block. // An example where this could happen would be if no-one could prove the mixed block. + // @note We prune the pending chain as part of the propose call. _testBlock("empty_block_1", false, prunableAt.unwrap()); assertEq(inbox.inProgress(), 3, "Invalid in progress"); From caad4ff1fd4085b2fea16f63a4c5f052f37b3284 Mon Sep 17 00:00:00 2001 From: just-mitch <68168980+just-mitch@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:19:18 -0400 Subject: [PATCH 25/43] feat: proofClaim in Rollup (#8636) Fix #8608 Add the proofClaim to the rollup. Update the `canPrune` logic to account for it. --- l1-contracts/src/core/Rollup.sol | 38 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 782a3910bce..b92f04cbdd2 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -55,6 +55,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; + uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; + uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; + uint256 public immutable L1_BLOCK_AT_GENESIS; IRegistry public immutable REGISTRY; IInbox public immutable INBOX; @@ -720,7 +723,26 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + /** + * @notice Get the epoch that should be proven + * + * @dev This is the epoch that should be proven. It does so by getting the epoch of the block + * following the last proven block. If there is no such block (i.e. the pending chain is + * the same as the proven chain), then revert. + * + * @return uint256 - The epoch to prove + */ + function getEpochToProve() public view override(IRollup) returns (uint256) { + if (tips.provenBlockNumber == tips.pendingBlockNumber) { + revert Errors.Rollup__NoEpochToProve(); + } else { + return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber); + } + } + function _prune() internal { + delete proofClaim; + uint256 pending = tips.pendingBlockNumber; // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. @@ -741,12 +763,20 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; - // TODO: #8608 adds a proof claim, which will allow us to prune the chain more aggressively. - // That is what will add a `CLAIM_DURATION` to the pruning logic. - if (currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION) { + // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. + // we prune the pending chain back to the end of epoch 1 if: + // - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2) + // - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted) + bool inClaimPhase = currentSlot + < startSlotOfPendingEpoch + Constants.AZTEC_EPOCH_DURATION + CLAIM_DURATION_IN_L2_SLOTS; + + bool claimExists = currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION + && proofClaim.epochToProve == oldestPendingEpoch && proofClaim.proposerClaimant != address(0); + + if (inClaimPhase || claimExists) { + // If we are in the claim phase, do not prune return false; } - return true; } From cfceaffa992e591f24da95bc5d52d607fd241996 Mon Sep 17 00:00:00 2001 From: Mitch Date: Thu, 19 Sep 2024 20:48:10 -0400 Subject: [PATCH 26/43] clean up escrow interface remove `TIMELINESS_PROVING_IN_SLOTS` update tests --- l1-contracts/src/core/Rollup.sol | 18 +++++++++++++++--- l1-contracts/test/Rollup.t.sol | 7 ++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index b92f04cbdd2..7881c2eec4b 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -55,9 +55,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; - uint256 public constant CLAIM_DURATION_IN_L2_SLOTS = 13; - uint256 public constant PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; - uint256 public immutable L1_BLOCK_AT_GENESIS; IRegistry public immutable REGISTRY; IInbox public immutable INBOX; @@ -740,7 +737,22 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } } + /** + * @notice Get the archive root of a specific block + * + * @param _blockNumber - The block number to get the archive root of + * + * @return bytes32 - The archive root of the block + */ + function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { + if (_blockNumber <= tips.pendingBlockNumber) { + return blocks[_blockNumber].archive; + } + return bytes32(0); + } + function _prune() internal { + // TODO #8656 delete proofClaim; uint256 pending = tips.pendingBlockNumber; diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index ca1a4c60b48..e3fe3bd4b4a 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -341,7 +341,7 @@ contract RollupTest is DecoderBase { rollup.prune(); } - function testPruneDuringPropose() public setUpFor("mixed_block_1") { + function testPrune() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false); assertEq(inbox.inProgress(), 3, "Invalid in progress"); @@ -370,6 +370,11 @@ contract RollupTest is DecoderBase { assertNotEq(rootMixed, bytes32(0), "Invalid root"); assertNotEq(minHeightMixed, 0, "Invalid min height"); + rollup.prune(); + assertEq(inbox.inProgress(), 3, "Invalid in progress"); + assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); + // @note We alter what slot is specified in the empty block! // This means that we keep the `empty_block_1` mostly as is, but replace the slot number // and timestamp as if it was created at a different point in time. This allow us to insert it From 7376ca387bdd60ec8138b63dbe9b7a59db7a93a3 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 11:39:33 +0000 Subject: [PATCH 27/43] Initial work --- l1-contracts/src/core/Leonidas.sol | 11 + l1-contracts/src/core/Rollup.sol | 266 ++++++++++-------- .../src/core/interfaces/ILeonidas.sol | 1 + l1-contracts/src/core/interfaces/IRollup.sol | 14 + .../archiver/src/archiver/data_retrieval.ts | 4 +- yarn-project/circuit-types/src/mocks.ts | 22 ++ .../prover_coordination/epoch_proof_quote.ts | 11 + .../e2e_json_coordination.test.ts | 215 +++++++++++++- 8 files changed, 421 insertions(+), 123 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 75390445d0b..bb6a69dd630 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -320,6 +320,17 @@ contract Leonidas is Ownable, ILeonidas { return _ts < GENESIS_TIME ? Slot.wrap(0) : SlotLib.fromTimestamp(_ts - GENESIS_TIME); } + /** + * @notice Computes the epoch at a specific slot + * + * @param _slotNumber - The slot number to compute the epoch for + * + * @return The computed epoch + */ + function getEpochAtSlot(uint256 _slotNumber) public pure override(ILeonidas) returns (uint256) { + return _slotNumber / EPOCH_DURATION; + } + /** * @notice Adds a validator to the set WITHOUT setting up the epoch * @param _validator - The validator to add diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 7881c2eec4b..27b52e2afea 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -159,62 +159,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { vkTreeRoot = _vkTreeRoot; } - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) - external - override(IRollup) - { - Slot currentSlot = getCurrentSlot(); - address currentProposer = getCurrentProposer(); - Epoch epochToProve = getEpochToProve(); - - if (currentProposer != address(0) && currentProposer != msg.sender) { - revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); - } - - if (_quote.quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); - } - - if (currentSlot.positionInEpoch() >= CLAIM_DURATION_IN_L2_SLOTS) { - revert Errors.Rollup__NotInClaimPhase( - currentSlot.positionInEpoch(), CLAIM_DURATION_IN_L2_SLOTS - ); - } - - // if the epoch to prove is not the one that has been claimed, - // then whatever is in the proofClaim is stale - if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { - revert Errors.Rollup__ProofRightAlreadyClaimed(); - } - - if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { - revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount - ); - } - - if (_quote.quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot); - } - - // We don't currently unstake, - // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. - // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); - - proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epochToProve, - basisPointFee: _quote.quote.basisPointFee, - bondAmount: _quote.quote.bondAmount, - bondProvider: _quote.quote.prover, - proposerClaimant: msg.sender - }); - - emit ProofRightClaimed( - epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot - ); - } - /** * @notice Publishes the body and propose the block * @dev `eth_log_handlers` rely on this function @@ -225,68 +169,17 @@ contract Rollup is Leonidas, IRollup, ITestRollup { * @param _signatures - Signatures from the validators * @param _body - The body of the L2 block */ - function propose( + function proposeAndClaim( bytes calldata _header, bytes32 _archive, bytes32 _blockHash, bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, - bytes calldata _body + bytes calldata _body, + DataStructures.EpochProofQuote calldata _quote ) external override(IRollup) { - if (_canPrune()) { - _prune(); - } - bytes32 txsEffectsHash = TxsDecoder.decode(_body); - - // Decode and validate header - HeaderLib.Header memory header = HeaderLib.decode(_header); - - bytes32 digest = keccak256(abi.encode(_archive, _txHashes)); - setupEpoch(); - _validateHeader({ - _header: header, - _signatures: _signatures, - _digest: digest, - _currentTime: Timestamp.wrap(block.timestamp), - _txEffectsHash: txsEffectsHash, - _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) - }); - - uint256 blockNumber = ++tips.pendingBlockNumber; - - blocks[blockNumber] = BlockLog({ - archive: _archive, - blockHash: _blockHash, - slotNumber: Slot.wrap(header.globalVariables.slotNumber) - }); - - // @note The block number here will always be >=1 as the genesis block is at 0 - bytes32 inHash = INBOX.consume(blockNumber); - if (header.contentCommitment.inHash != inHash) { - revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); - } - - // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim - // Min size = smallest path of the rollup tree + 1 - (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); - uint256 l2ToL1TreeMinHeight = min + 1; - OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight); - - emit L2BlockProposed(blockNumber, _archive); - - // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. - if (blockNumber <= assumeProvenThroughBlockNumber) { - tips.provenBlockNumber = blockNumber; - - if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { - // @note This will currently fail if there are insufficient funds in the bridge - // which WILL happen for the old version after an upgrade where the bridge follow. - // Consider allowing a failure. See #7938. - FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); - } - - emit L2ProofVerified(blockNumber, "CHEAT"); - } + propose(_header, _archive, _blockHash, _txHashes, _signatures, _body); + claimEpochProofRight(_quote); } /** @@ -694,6 +587,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags); } + function nextEpochToClaim() external view override(IRollup) returns (uint256) { + uint256 epochClaimed = proofClaim.epochToProve; + if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) { + return 0; + } + return 1 + epochClaimed; + } + function computeTxsEffectsHash(bytes calldata _body) external pure @@ -703,6 +604,145 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return TxsDecoder.decode(_body); } + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) + public + override(IRollup) + { + validateEpochProofRightClaim(_quote); + + uint256 currentSlot = getCurrentSlot(); + uint256 epochToProve = getEpochToProve(); + + // We don't currently unstake, + // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. + // Blocked on submitting epoch proofs to this contract. + address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount); + + proofClaim = DataStructures.EpochProofClaim({ + epochToProve: epochToProve, + basisPointFee: _quote.basisPointFee, + bondAmount: _quote.bondAmount, + bondProvider: bondProvider, + proposerClaimant: msg.sender + }); + + emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot); + } + + /** + * @notice Publishes the body and propose the block + * @dev `eth_log_handlers` rely on this function + * + * @param _header - The L2 block header + * @param _archive - A root of the archive tree after the L2 block is applied + * @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit + * @param _signatures - Signatures from the validators + * @param _body - The body of the L2 block + */ + function propose( + bytes calldata _header, + bytes32 _archive, + bytes32 _blockHash, + bytes32[] memory _txHashes, + SignatureLib.Signature[] memory _signatures, + bytes calldata _body + ) public override(IRollup) { + if (_canPrune()) { + _prune(); + } + bytes32 txsEffectsHash = TxsDecoder.decode(_body); + + // Decode and validate header + HeaderLib.Header memory header = HeaderLib.decode(_header); + + bytes32 digest = keccak256(abi.encode(_archive, _txHashes)); + setupEpoch(); + _validateHeader({ + _header: header, + _signatures: _signatures, + _digest: digest, + _currentTime: block.timestamp, + _txEffectsHash: txsEffectsHash, + _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) + }); + + uint256 blockNumber = ++tips.pendingBlockNumber; + + blocks[blockNumber] = BlockLog({ + archive: _archive, + blockHash: _blockHash, + slotNumber: header.globalVariables.slotNumber.toUint128() + }); + + // @note The block number here will always be >=1 as the genesis block is at 0 + bytes32 inHash = INBOX.consume(blockNumber); + if (header.contentCommitment.inHash != inHash) { + revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); + } + + // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim + // Min size = smallest path of the rollup tree + 1 + (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); + uint256 l2ToL1TreeMinHeight = min + 1; + OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight); + + emit L2BlockProposed(blockNumber, _archive); + + // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. + if (blockNumber <= assumeProvenThroughBlockNumber) { + tips.provenBlockNumber = blockNumber; + + if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { + // @note This will currently fail if there are insufficient funds in the bridge + // which WILL happen for the old version after an upgrade where the bridge follow. + // Consider allowing a failure. See #7938. + FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); + } + + emit L2ProofVerified(blockNumber, "CHEAT"); + } + } + + function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) + public + view + override(IRollup) + { + uint256 currentSlot = getCurrentSlot(); + address currentProposer = getCurrentProposer(); + uint256 epochToProve = getEpochToProve(); + + if (currentProposer != address(0) && currentProposer != msg.sender) { + revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); + } + + if (_quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); + } + + if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { + revert Errors.Rollup__NotInClaimPhase( + currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS + ); + } + + // if the epoch to prove is not the one that has been claimed, + // then whatever is in the proofClaim is stale + if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { + revert Errors.Rollup__ProofRightAlreadyClaimed(); + } + + if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + revert Errors.Rollup__InsufficientBondAmount( + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount + ); + } + + if (_quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); + } + } + /** * @notice Get the current archive root * @@ -733,7 +773,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { if (tips.provenBlockNumber == tips.pendingBlockNumber) { revert Errors.Rollup__NoEpochToProve(); } else { - return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber); + return getEpochAt(getTimestampForSlot(blocks[getProvenBlockNumber() + 1].slotNumber)); } } diff --git a/l1-contracts/src/core/interfaces/ILeonidas.sol b/l1-contracts/src/core/interfaces/ILeonidas.sol index 6a63f52a4da..ece101d7277 100644 --- a/l1-contracts/src/core/interfaces/ILeonidas.sol +++ b/l1-contracts/src/core/interfaces/ILeonidas.sol @@ -32,4 +32,5 @@ interface ILeonidas { function getEpochAt(Timestamp _ts) external view returns (Epoch); function getSlotAt(Timestamp _ts) external view returns (Slot); + function getEpochAtSlot(Slot _slotNumber) external view returns (Epoch); } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 3ed428cfab8..b189bec92b7 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -41,6 +41,16 @@ interface IRollup { bytes calldata _body ) external; + function proposeAndClaim( + bytes calldata _header, + bytes32 _archive, + bytes32 _blockHash, + bytes32[] memory _txHashes, + SignatureLib.Signature[] memory _signatures, + bytes calldata _body, + DataStructures.EpochProofQuote calldata _quote + ) external; + function submitBlockRootProof( bytes calldata _header, bytes32 _archive, @@ -105,5 +115,9 @@ interface IRollup { function getProvenBlockNumber() external view returns (uint256); function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (Epoch); + function nextEpochToClaim() external view returns (Epoch); + function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) + external + view; function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); } diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index b447162c576..6b3b5228220 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -134,7 +134,9 @@ async function getBlockFromRollupTx( data, }); - if (!(functionName === 'propose')) { + const allowedMethods = ['propose', 'proposeAndClaim']; + + if (!allowedMethods.includes(functionName)) { throw new Error(`Unexpected method called ${functionName}`); } const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex]; diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index db9c3bc9f87..f594a081ab2 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -2,6 +2,7 @@ import { AztecAddress, CallContext, ClientIvcProof, + type EthAddress, GasSettings, LogHash, MAX_ENCRYPTED_LOGS_PER_TX, @@ -28,11 +29,14 @@ import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd, times } from '@aztec/foundation/collection'; import { randomBytes } from '@aztec/foundation/crypto'; +import { Signature } from '@aztec/foundation/eth-signature'; import { Fr } from '@aztec/foundation/fields'; import { type ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; import { EncryptedNoteTxL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js'; import { ExtendedNote, UniqueNote } from './notes/index.js'; +import { EpochProofQuote } from './prover_coordination/epoch_proof_quote.js'; +import { EpochProofQuotePayload } from './prover_coordination/epoch_proof_quote_payload.js'; import { PublicExecutionRequest } from './public_execution_request.js'; import { NestedProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js'; @@ -223,6 +227,24 @@ export const mockSimulatedTx = (seed = 1, hasLogs = true) => { return new SimulatedTx(tx, dec, output); }; +export const mockEpochProofQuote = ( + epochToProve: bigint, + validUntilSlot: bigint, + bondAmount: bigint, + rollupAddress: EthAddress, + basisPointFee: number, +) => { + const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( + epochToProve, + validUntilSlot, + bondAmount, + rollupAddress, + basisPointFee, + ); + const sig: Signature = Signature.empty(); + return new EpochProofQuote(quotePayload, sig); +}; + export const randomContractArtifact = (): ContractArtifact => ({ name: randomBytes(4).toString('hex'), functions: [], diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index d5c2f40e296..0e88a7ebffa 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -52,4 +52,15 @@ export class EpochProofQuote extends Gossipable { return this.sender; } + + toViemArgs() { + return { + epochToProve: this.payload.epochToProve, + validUntilSlot: this.payload.validUntilSlot, + bondAmount: this.payload.bondAmount, + rollupAddress: this.payload.rollupAddress, + basisPointFee: this.payload.basisPointFee, + signature: this.signature.toViemSignature(), + }; + } } diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts index 89172315e9a..fa8478d0908 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -1,7 +1,13 @@ -import { type DebugLogger, createDebugLogger } from '@aztec/aztec.js'; -import { makeRandomEpochProofQuote } from '@aztec/p2p'; +import { getSchnorrAccount } from '@aztec/accounts/schnorr'; +import { type AccountWalletWithSecretKey, type DebugLogger, EthCheatCodes, createDebugLogger } from '@aztec/aztec.js'; +import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types'; +import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js'; +import { times } from '@aztec/foundation/collection'; +import { RollupAbi } from '@aztec/l1-artifacts'; +import { StatefulTestContract } from '@aztec/noir-contracts.js'; import { beforeAll } from '@jest/globals'; +import { type PublicClient, getAddress, getContract } from 'viem'; import { type ISnapshotManager, @@ -16,6 +22,12 @@ import { // the coordination through L1 between the sequencer and the prover node. describe('e2e_prover_node', () => { let ctx: SubsystemsContext; + let wallet: AccountWalletWithSecretKey; + let recipient: AztecAddress; + let contract: StatefulTestContract; + let rollupContract: any; + let publicClient: PublicClient; + let cc: EthCheatCodes; let logger: DebugLogger; let snapshotManager: ISnapshotManager; @@ -24,17 +36,202 @@ describe('e2e_prover_node', () => { logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); - await snapshotManager.snapshot('setup', addAccounts(2, logger)); + logger.info(`1`); + + await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { + const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1)); + await Promise.all(accountManagers.map(a => a.register())); + const wallets = await Promise.all(accountManagers.map(a => a.getWallet())); + wallets.forEach((w, i) => logger.verbose(`Wallet ${i} address: ${w.getAddress()}`)); + wallet = wallets[0]; + recipient = wallets[1].getAddress(); + }); + + await snapshotManager.snapshot( + 'deploy-test-contract', + async () => { + const owner = wallet.getAddress(); + const contract = await StatefulTestContract.deploy(wallet, owner, owner, 42).send().deployed(); + return { contractAddress: contract.address }; + }, + async ({ contractAddress }) => { + contract = await StatefulTestContract.at(contractAddress, wallet); + }, + ); ctx = await snapshotManager.setup(); + + await ctx.proverNode.stop(); + + cc = new EthCheatCodes(ctx.aztecNodeConfig.l1RpcUrl); + + publicClient = ctx.deployL1ContractsValues.publicClient; + rollupContract = getContract({ + address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()), + abi: RollupAbi, + client: ctx.deployL1ContractsValues.walletClient, + }); }); - it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { - const { quote } = makeRandomEpochProofQuote(); + // it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { + // const { quote } = makeRandomEpochProofQuote(); + + // await ctx.proverNode.sendEpochProofQuote(quote); + // const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); + // expect(receivedQuotes.length).toBe(1); + // expect(receivedQuotes[0]).toEqual(quote); + // }); + + const expectProofClaimOnL1 = async (quote: EpochProofQuote, _: EthAddress) => { + const claimFromContract = await rollupContract.read.proofClaim(); + expect(claimFromContract[0]).toEqual(quote.payload.epochToProve); + expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee)); + expect(claimFromContract[2]).toEqual(quote.payload.bondAmount); + //expect(claimFromContract[4]).toEqual(proposer.toString()); + }; + + const getL1Timestamp = async () => { + return BigInt((await publicClient.getBlock()).timestamp); + }; + + const getSlot = async () => { + const ts = await getL1Timestamp(); + return await rollupContract.read.getSlotAt([ts]); + }; + + const getEpoch = async () => { + const slotNumber = await getSlot(); + return await rollupContract.read.getEpochAtSlot([slotNumber]); + }; + + const getPendingBlockNumber = async () => { + return await rollupContract.read.getPendingBlockNumber(); + }; + + const getProvenBlockNumber = async () => { + return await rollupContract.read.getProvenBlockNumber(); + }; + + const getEpochToProve = async () => { + return await rollupContract.read.getEpochToProve(); + }; + + const getTimestampForSlot = async (slotNumber: bigint) => { + return await rollupContract.read.getTimestampForSlot([slotNumber]); + }; + + const verifyQuote = async (quote: EpochProofQuote) => { + try { + const args = [quote.toViemArgs()] as const; + await rollupContract.read.validateEpochProofRightClaim(args); + logger.info('QUOTE VERIFIED'); + } catch (error) { + console.log(error); + } + }; + + const logState = async () => { + logger.info(`Pending block: ${await getPendingBlockNumber()}`); + logger.info(`Proven block: ${await getProvenBlockNumber()}`); + logger.info(`Slot number: ${await getSlot()}`); + logger.info(`Epoch number: ${await getEpoch()}`); + logger.info(`Epoch to prove ${await getEpochToProve()}`); + }; + + const advanceToNextEpoch = async () => { + const slot = await getSlot(); + const slotsUntilNextEpoch = BigInt(AZTEC_EPOCH_DURATION) - (slot % BigInt(AZTEC_EPOCH_DURATION)) + 1n; + const timeToNextEpoch = slotsUntilNextEpoch * BigInt(AZTEC_SLOT_DURATION); + logger.info(`SLOTS TO NEXT EPOCH ${slotsUntilNextEpoch}`); + const l1Timestamp = await getL1Timestamp(); + await cc.warp(Number(l1Timestamp + timeToNextEpoch)); + await logState(); + }; + + it('Sequencer selects best valid proving quote for each block', async () => { + // We want to create a set of proving quotes, some valid and some invalid + // The sequencer should select the cheapest valid quote when it proposes the block + logger.info(`Start`); + + // Here we are creating a proof quote for epoch 0, this will NOT get used yet + const quoteForEpoch0 = mockEpochProofQuote( + 0n, // epoch 0 + BigInt(AZTEC_EPOCH_DURATION + 10), // valid until slot 10 into epoch 1 + 10000n, + EthAddress.random(), + 1, + ); + + // Send in the quote + await ctx.proverNode.sendEpochProofQuote(quoteForEpoch0); + + // Build a block, this should NOT use the above quote as it is for the current epoch (0) + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + await logState(); + + const epoch0BlockNumber = await getPendingBlockNumber(); + + // Verify that the claim state on L1 is unitialised + const uninitialisedProofClaim = mockEpochProofQuote( + 0n, // epoch 0 + BigInt(0), + 0n, + EthAddress.random(), + 0, + ); + + // The rollup contract should have an uninitialised proof claim struct + await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.random()); + + // Now go to epoch 1 + await advanceToNextEpoch(); + + const blockSlot = await getSlot(); + + logger.info(`TIMESTAMP FOR SLOT: ${await getTimestampForSlot(blockSlot)}`); + + await logState(); + + // Build a block in epoch 1, we should see the quote for epoch 0 submitted earlier published to L1 + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + // Check it was published + await expectProofClaimOnL1(quoteForEpoch0, EthAddress.random()); + + // now 'prove' epoch 0 + await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); + + logger.info(`SET PROVEN BLOCK NUMBER`); + + await logState(); + + // Now go to epoch 2 + await advanceToNextEpoch(); + + const currentSlot = await getSlot(); + + // Now create a number of quotes, some valid some invalid for epoch 1, the lowest priced valid quote should be chosen + const validQuotes = times(3, (i: number) => + mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i), + ); + + // Check the L1 verification of this quote + await verifyQuote(validQuotes[0]); + + const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1); + + const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes]; + + //await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); + + // now build another block and we should see the best valid quote being published + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + const expectedQuote = validQuotes[0]; - await ctx.proverNode.sendEpochProofQuote(quote); - const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); - expect(receivedQuotes.length).toBe(1); - expect(receivedQuotes[0]).toEqual(quote); + await expectProofClaimOnL1(expectedQuote, EthAddress.random()); }); }); From c24355eeeb85d687e85d5c570849f03e08bc909d Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 16:17:25 +0000 Subject: [PATCH 28/43] Passing test --- l1-contracts/src/core/Rollup.sol | 3 +- yarn-project/aztec.js/src/index.ts | 2 + .../e2e_json_coordination.test.ts | 83 ++--- .../src/publisher/l1-publisher.ts | 252 ++++++++----- .../src/sequencer/sequencer.test.ts | 336 +++++++++++++++++- .../src/sequencer/sequencer.ts | 53 ++- 6 files changed, 589 insertions(+), 140 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 27b52e2afea..2e8ae5f9673 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -812,7 +812,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } uint256 currentSlot = getCurrentSlot(); - uint256 oldestPendingEpoch = getEpochAt(blocks[tips.provenBlockNumber + 1].slotNumber); + uint256 oldestPendingEpoch = + getEpochAt(getTimestampForSlot(blocks[tips.provenBlockNumber + 1].slotNumber)); uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index dba2233180c..08f9c3b460c 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -133,9 +133,11 @@ export { createAztecNodeClient, merkleTreeIds, mockTx, + mockEpochProofQuote, TaggedLog, L1NotePayload, L1EventPayload, + EpochProofQuote, } from '@aztec/circuit-types'; export { NodeInfo } from '@aztec/types/interfaces'; diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts index fa8478d0908..8ebe827b0bc 100644 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts +++ b/yarn-project/end-to-end/src/prover-coordination/e2e_json_coordination.test.ts @@ -1,6 +1,12 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; -import { type AccountWalletWithSecretKey, type DebugLogger, EthCheatCodes, createDebugLogger } from '@aztec/aztec.js'; -import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types'; +import { + type AccountWalletWithSecretKey, + type DebugLogger, + type EpochProofQuote, + EthCheatCodes, + createDebugLogger, + mockEpochProofQuote, +} from '@aztec/aztec.js'; import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js'; import { times } from '@aztec/foundation/collection'; import { RollupAbi } from '@aztec/l1-artifacts'; @@ -28,6 +34,7 @@ describe('e2e_prover_node', () => { let rollupContract: any; let publicClient: PublicClient; let cc: EthCheatCodes; + let publisherAddress: EthAddress; let logger: DebugLogger; let snapshotManager: ISnapshotManager; @@ -36,8 +43,6 @@ describe('e2e_prover_node', () => { logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination'); snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH); - logger.info(`1`); - await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1)); await Promise.all(accountManagers.map(a => a.register())); @@ -66,6 +71,7 @@ describe('e2e_prover_node', () => { cc = new EthCheatCodes(ctx.aztecNodeConfig.l1RpcUrl); publicClient = ctx.deployL1ContractsValues.publicClient; + publisherAddress = EthAddress.fromString(ctx.deployL1ContractsValues.walletClient.account.address); rollupContract = getContract({ address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()), abi: RollupAbi, @@ -73,21 +79,12 @@ describe('e2e_prover_node', () => { }); }); - // it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => { - // const { quote } = makeRandomEpochProofQuote(); - - // await ctx.proverNode.sendEpochProofQuote(quote); - // const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve); - // expect(receivedQuotes.length).toBe(1); - // expect(receivedQuotes[0]).toEqual(quote); - // }); - - const expectProofClaimOnL1 = async (quote: EpochProofQuote, _: EthAddress) => { + const expectProofClaimOnL1 = async (quote: EpochProofQuote, proposerAddress: EthAddress) => { const claimFromContract = await rollupContract.read.proofClaim(); expect(claimFromContract[0]).toEqual(quote.payload.epochToProve); expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee)); expect(claimFromContract[2]).toEqual(quote.payload.bondAmount); - //expect(claimFromContract[4]).toEqual(proposer.toString()); + expect(claimFromContract[4]).toEqual(proposerAddress.toChecksumString()); }; const getL1Timestamp = async () => { @@ -116,20 +113,6 @@ describe('e2e_prover_node', () => { return await rollupContract.read.getEpochToProve(); }; - const getTimestampForSlot = async (slotNumber: bigint) => { - return await rollupContract.read.getTimestampForSlot([slotNumber]); - }; - - const verifyQuote = async (quote: EpochProofQuote) => { - try { - const args = [quote.toViemArgs()] as const; - await rollupContract.read.validateEpochProofRightClaim(args); - logger.info('QUOTE VERIFIED'); - } catch (error) { - console.log(error); - } - }; - const logState = async () => { logger.info(`Pending block: ${await getPendingBlockNumber()}`); logger.info(`Proven block: ${await getProvenBlockNumber()}`); @@ -142,7 +125,6 @@ describe('e2e_prover_node', () => { const slot = await getSlot(); const slotsUntilNextEpoch = BigInt(AZTEC_EPOCH_DURATION) - (slot % BigInt(AZTEC_EPOCH_DURATION)) + 1n; const timeToNextEpoch = slotsUntilNextEpoch * BigInt(AZTEC_SLOT_DURATION); - logger.info(`SLOTS TO NEXT EPOCH ${slotsUntilNextEpoch}`); const l1Timestamp = await getL1Timestamp(); await cc.warp(Number(l1Timestamp + timeToNextEpoch)); await logState(); @@ -151,7 +133,6 @@ describe('e2e_prover_node', () => { it('Sequencer selects best valid proving quote for each block', async () => { // We want to create a set of proving quotes, some valid and some invalid // The sequencer should select the cheapest valid quote when it proposes the block - logger.info(`Start`); // Here we are creating a proof quote for epoch 0, this will NOT get used yet const quoteForEpoch0 = mockEpochProofQuote( @@ -182,28 +163,24 @@ describe('e2e_prover_node', () => { ); // The rollup contract should have an uninitialised proof claim struct - await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.random()); + await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.ZERO); // Now go to epoch 1 await advanceToNextEpoch(); - const blockSlot = await getSlot(); - - logger.info(`TIMESTAMP FOR SLOT: ${await getTimestampForSlot(blockSlot)}`); - await logState(); // Build a block in epoch 1, we should see the quote for epoch 0 submitted earlier published to L1 await contract.methods.create_note(recipient, recipient, 10).send().wait(); + const epoch1BlockNumber = await getPendingBlockNumber(); + // Check it was published - await expectProofClaimOnL1(quoteForEpoch0, EthAddress.random()); + await expectProofClaimOnL1(quoteForEpoch0, publisherAddress); // now 'prove' epoch 0 await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); - logger.info(`SET PROVEN BLOCK NUMBER`); - await logState(); // Now go to epoch 2 @@ -216,22 +193,38 @@ describe('e2e_prover_node', () => { mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i), ); - // Check the L1 verification of this quote - await verifyQuote(validQuotes[0]); - const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1); const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2); - const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes]; + const proofQuoteInsufficientBond = mockEpochProofQuote(1n, currentSlot + 4n, 0n, EthAddress.random(), 3); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInsufficientBond]; - //await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); + await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x))); // now build another block and we should see the best valid quote being published await contract.methods.create_note(recipient, recipient, 10).send().wait(); const expectedQuote = validQuotes[0]; - await expectProofClaimOnL1(expectedQuote, EthAddress.random()); + await expectProofClaimOnL1(expectedQuote, publisherAddress); + + // building another block should succeed, we should not try and submit another quote + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + await expectProofClaimOnL1(expectedQuote, publisherAddress); + + // now 'prove' epoch 1 + await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch1BlockNumber)]); + + // Now go to epoch 3 + await advanceToNextEpoch(); + + // now build another block and we should see that no claim is published as nothing is valid + await contract.methods.create_note(recipient, recipient, 10).send().wait(); + + // The quote state on L1 is the same as before + await expectProofClaimOnL1(expectedQuote, publisherAddress); }); }); diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 447e42b3dcf..4c888a0bf0c 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -1,4 +1,10 @@ -import { ConsensusPayload, type L2Block, type TxHash, getHashedSignaturePayload } from '@aztec/circuit-types'; +import { + ConsensusPayload, + type EpochProofQuote, + type L2Block, + type TxHash, + getHashedSignaturePayload, +} from '@aztec/circuit-types'; import { type L1PublishBlockStats, type L1PublishProofStats } from '@aztec/circuit-types/stats'; import { ETHEREUM_SLOT_DURATION, EthAddress, type FeeRecipient, type Header, type Proof } from '@aztec/circuits.js'; import { createEthereumChain } from '@aztec/ethereum'; @@ -132,6 +138,7 @@ export class L1Publisher { private account: PrivateKeyAccount; public static PROPOSE_GAS_GUESS: bigint = 500_000n; + public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = 600_000n; constructor(config: TxSenderConfig & PublisherConfig, client: TelemetryClient) { this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000; @@ -178,6 +185,33 @@ export class L1Publisher { return [slot, blockNumber]; } + /** + * @notice Queries 'proofClaim' on the Rollup to determine if we are able to claim the given epoch for proving + * Only checks to see if the given epoch is claimed + * + * @param epochNumber - The epoch being queried + * @return True or False + */ + public async canClaimEpoch(epochNumber: bigint): Promise { + const nextToBeClaimed = await this.rollupContract.read.nextEpochToClaim(); + return epochNumber == nextToBeClaimed; + } + + public async getEpochForSlotNumber(slotNumber: bigint): Promise { + return await this.rollupContract.read.getEpochAtSlot([slotNumber]); + } + + public async validateProofQuote(quote: EpochProofQuote): Promise { + const args = [quote.toViemArgs()] as const; + try { + await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account }); + } catch (err) { + //console.log(err); + return undefined; + } + return quote; + } + /** * @notice Will call `validateHeader` to make sure that it is possible to propose * @@ -245,7 +279,12 @@ export class L1Publisher { * @param block - L2 block to propose. * @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise. */ - public async proposeL2Block(block: L2Block, attestations?: Signature[], txHashes?: TxHash[]): Promise { + public async proposeL2Block( + block: L2Block, + attestations?: Signature[], + txHashes?: TxHash[], + proofQuote?: EpochProofQuote, + ): Promise { const ctx = { blockNumber: block.number, slotNumber: block.header.globalVariables.slotNumber.toBigInt(), @@ -265,53 +304,58 @@ export class L1Publisher { }; // Publish body and propose block (if not already published) - if (!this.interrupted) { - const timer = new Timer(); - - // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available - // This means that we can avoid the simulation issues in later checks. - // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which - // make time consistency checks break. - await this.validateBlockForSubmission(block.header, { - digest: digest.toBuffer(), - signatures: attestations ?? [], - }); + if (this.interrupted) { + this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx); + return false; + } - const txHash = await this.sendProposeTx(proposeTxArgs); + const timer = new Timer(); - if (!txHash) { - this.log.info(`Failed to publish block ${block.number} to L1`, ctx); - return false; - } + // @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available + // This means that we can avoid the simulation issues in later checks. + // By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which + // make time consistency checks break. + await this.validateBlockForSubmission(block.header, { + digest: digest.toBuffer(), + signatures: attestations ?? [], + }); - const receipt = await this.getTransactionReceipt(txHash); - if (!receipt) { - this.log.info(`Failed to get receipt for tx ${txHash}`, ctx); - return false; - } + this.log.verbose(`Submitting propose transaction with `); - // Tx was mined successfully - if (receipt.status) { - const tx = await this.getTransactionStats(txHash); - const stats: L1PublishBlockStats = { - ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), - ...pick(tx!, 'calldataGas', 'calldataSize'), - ...block.getStats(), - eventName: 'rollup-published-to-l1', - }; - this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx }); - this.metrics.recordProcessBlockTx(timer.ms(), stats); + const txHash = proofQuote + ? await this.sendProposeAndClaimTx(proposeTxArgs, proofQuote) + : await this.sendProposeTx(proposeTxArgs); - return true; - } + if (!txHash) { + this.log.info(`Failed to publish block ${block.number} to L1`, ctx); + return false; + } - this.metrics.recordFailedTx('process'); + const receipt = await this.getTransactionReceipt(txHash); + if (!receipt) { + this.log.info(`Failed to get receipt for tx ${txHash}`, ctx); + return false; + } - this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx); - await this.sleepOrInterrupted(); + // Tx was mined successfully + if (receipt.status) { + const tx = await this.getTransactionStats(txHash); + const stats: L1PublishBlockStats = { + ...pick(receipt, 'gasPrice', 'gasUsed', 'transactionHash'), + ...pick(tx!, 'calldataGas', 'calldataSize'), + ...block.getStats(), + eventName: 'rollup-published-to-l1', + }; + this.log.info(`Published L2 block to L1 rollup contract`, { ...stats, ...ctx }); + this.metrics.recordProcessBlockTx(timer.ms(), stats); + + return true; } - this.log.verbose('L2 block data syncing interrupted while processing blocks.', ctx); + this.metrics.recordFailedTx('process'); + + this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`, ctx); + await this.sleepOrInterrupted(); return false; } @@ -479,47 +523,95 @@ export class L1Publisher { } private async sendProposeTx(encodedData: L1ProcessArgs): Promise { - if (!this.interrupted) { - try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); + if (this.interrupted) { + return; + } + try { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - ] as const; - - return await this.rollupContract.write.propose(args, { - account: this.account, - gas: gasGuesstimate, - }); - } catch (err) { - prettyLogVeimError(err, this.log); - this.log.error(`Rollup publish failed`, err); - return undefined; - } + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return await this.rollupContract.write.propose(args, { + account: this.account, + gas: gasGuesstimate, + }); + } catch (err) { + prettyLogVeimError(err, this.log); + this.log.error(`Rollup publish failed`, err); + return undefined; + } + } + + private async sendProposeAndClaimTx(encodedData: L1ProcessArgs, quote: EpochProofQuote): Promise { + if (this.interrupted) { + return; + } + try { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); + + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + quote.toViemArgs(), + ] as const; + + return await this.rollupContract.write.proposeAndClaim(args, { + account: this.account, + gas: gasGuesstimate, + }); + } catch (err) { + prettyLogVeimError(err, this.log); + this.log.error(`Rollup publish failed`, err); + return undefined; } } diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 2b20ac57113..4c1d79c1540 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -3,6 +3,7 @@ import { BlockProposal, type BlockSimulator, ConsensusPayload, + type EpochProofQuote, type L1ToL2MessageSource, L2Block, type L2BlockSource, @@ -18,9 +19,11 @@ import { WorldStateRunningState, type WorldStateSynchronizer, makeProcessedTx, + mockEpochProofQuote, mockTxForRollup, } from '@aztec/circuit-types'; import { + AZTEC_EPOCH_DURATION, AztecAddress, EthAddress, Fr, @@ -119,12 +122,12 @@ describe('sequencer', () => { blockSimulator = mock(); p2p = mock({ - getStatus: () => Promise.resolve({ state: P2PClientState.IDLE, syncedToL2Block: lastBlockNumber }), + getStatus: mockFn().mockResolvedValue({ state: P2PClientState.IDLE, syncedToL2Block: lastBlockNumber }), }); worldState = mock({ getLatest: () => merkleTreeOps, - status: () => Promise.resolve({ state: WorldStateRunningState.IDLE, syncedToL2Block: lastBlockNumber }), + status: mockFn().mockResolvedValue({ state: WorldStateRunningState.IDLE, syncedToL2Block: lastBlockNumber }), }); publicProcessor = mock({ @@ -145,7 +148,7 @@ describe('sequencer', () => { l1ToL2MessageSource = mock({ getL1ToL2Messages: () => Promise.resolve(Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(Fr.ZERO)), - getBlockNumber: () => Promise.resolve(lastBlockNumber), + getBlockNumber: mockFn().mockResolvedValue(lastBlockNumber), }); // all txs use the same allowed FPC class @@ -208,7 +211,7 @@ describe('sequencer', () => { ); // Ok, we have an issue that we never actually call the process L2 block expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash]); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -257,7 +260,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash]); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -300,7 +303,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(p2p.deleteTxs).toHaveBeenCalledWith([doubleSpendTx.getTxHash()]); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -339,7 +342,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(p2p.deleteTxs).toHaveBeenCalledWith([invalidChainTx.getTxHash()]); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -380,7 +383,7 @@ describe('sequencer', () => { mockedGlobalVariables, Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), validTxHashes, undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -431,7 +434,7 @@ describe('sequencer', () => { Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), txHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), txHashes, undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -482,7 +485,7 @@ describe('sequencer', () => { Array(NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP).fill(new Fr(0n)), ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), []); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [], undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -536,7 +539,7 @@ describe('sequencer', () => { ); expect(publisher.proposeL2Block).toHaveBeenCalledTimes(1); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), postFlushTxHashes); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), postFlushTxHashes, undefined); expect(blockSimulator.cancel).toHaveBeenCalledTimes(0); }); @@ -580,6 +583,317 @@ describe('sequencer', () => { expect(publisher.proposeL2Block).not.toHaveBeenCalled(); }); + + describe('Handling proof quotes', () => { + let txHash: TxHash; + let currentEpoch = 0n; + const setupForBlockNumber = (blockNumber: number) => { + currentEpoch = BigInt(blockNumber) / BigInt(AZTEC_EPOCH_DURATION); + // Create a new block and header + block = L2Block.random(blockNumber); + + mockedGlobalVariables = new GlobalVariables( + chainId, + version, + block.header.globalVariables.blockNumber, + block.header.globalVariables.slotNumber, + Fr.ZERO, + coinbase, + feeRecipient, + gasFees, + ); + + worldState.status.mockResolvedValue({ + state: WorldStateRunningState.IDLE, + syncedToL2Block: block.header.globalVariables.blockNumber.toNumber() - 1, + }); + + p2p.getStatus.mockResolvedValue({ + syncedToL2Block: block.header.globalVariables.blockNumber.toNumber() - 1, + state: P2PClientState.IDLE, + }); + + l2BlockSource.getBlockNumber.mockResolvedValue(block.header.globalVariables.blockNumber.toNumber() - 1); + + l1ToL2MessageSource.getBlockNumber.mockResolvedValue(block.header.globalVariables.blockNumber.toNumber() - 1); + + globalVariableBuilder.buildGlobalVariables.mockResolvedValue(mockedGlobalVariables); + + publisher.canProposeAtNextEthBlock.mockResolvedValue([ + block.header.globalVariables.slotNumber.toBigInt(), + block.header.globalVariables.blockNumber.toBigInt(), + ]); + + publisher.getEpochForSlotNumber.mockImplementation((slotNumber: bigint) => + Promise.resolve(slotNumber / BigInt(AZTEC_EPOCH_DURATION)), + ); + + const tx = mockTxForRollup(); + tx.data.constants.txContext.chainId = chainId; + txHash = tx.getTxHash(); + const result: ProvingSuccess = { + status: PROVING_STATUS.SUCCESS, + }; + const ticket: ProvingTicket = { + provingPromise: Promise.resolve(result), + }; + + p2p.getTxs.mockReturnValue([tx]); + blockSimulator.startNewBlock.mockResolvedValueOnce(ticket); + blockSimulator.finaliseBlock.mockResolvedValue({ block }); + }; + + it('submits a valid proof quote with a block', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote); + }); + + it('does not claim the epoch previous to the first', async () => { + const blockNumber = 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + 0n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote); + }); + + it('does not submit a quote with an expired slot number', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + // Slot number expired + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('does not submit a valid quote if unable to claim epoch', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockResolvedValue(false); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('does not submit an invalid quote', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + const proofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(_ => Promise.resolve(undefined)); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); + }); + + it('only selects valid quotes', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + // Create 1 valid quote and 3 that have a higher fee but are invalid + const validProofQuote = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 1, + ); + + const proofQuoteInvalidSlot = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + const proofQuoteInvalidEpoch = mockEpochProofQuote( + currentEpoch, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + // This is deemed invalid by the contract, we identify it by a fee of 2 + const proofQuoteInvalid = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 2, + ); + + const allQuotes = [validProofQuote, proofQuoteInvalidSlot, proofQuoteInvalidEpoch, proofQuoteInvalid]; + + p2p.getEpochProofQuotes.mockResolvedValue(allQuotes); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(p => + Promise.resolve(p.payload.basisPointFee === 2 ? undefined : p), + ); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validProofQuote); + }); + + it('selects the lowest cost valid quote', async () => { + const blockNumber = AZTEC_EPOCH_DURATION + 1; + setupForBlockNumber(blockNumber); + + // Create 3 valid quotes with different fees. + // And 3 invalid quotes with lower fees + // We should select the lowest cost valid quote + const validQuotes = times(3, (i: number) => + mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 10 + i, + ), + ); + + const proofQuoteInvalidSlot = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 1, + ); + + const proofQuoteInvalidEpoch = mockEpochProofQuote( + currentEpoch, + block.header.globalVariables.slotNumber.toBigInt() - 1n, + 10000n, + EthAddress.random(), + 2, + ); + + // This is deemed invalid by the contract, we identify it by it's fee + const proofQuoteInvalid = mockEpochProofQuote( + currentEpoch - 1n, + block.header.globalVariables.slotNumber.toBigInt() + 1n, + 10000n, + EthAddress.random(), + 3, + ); + + const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInvalid]; + + p2p.getEpochProofQuotes.mockResolvedValue(allQuotes); + publisher.proposeL2Block.mockResolvedValueOnce(true); + + // Quote is reported as invalid + publisher.validateProofQuote.mockImplementation(p => + Promise.resolve(p.payload.basisPointFee === 3 ? undefined : p), + ); + + // The previous epoch can be claimed + publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => + Promise.resolve(epochNumber === currentEpoch - 1n), + ); + + await sequencer.initialSync(); + await sequencer.work(); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validQuotes[0]); + }); + }); }); class TestSubject extends Sequencer { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 04426ee93d0..f6a04198071 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,5 +1,6 @@ import { type BlockAttestation, + type EpochProofQuote, type L1ToL2MessageSource, type L2Block, type L2BlockSource, @@ -404,6 +405,14 @@ export class Sequencer { const newGlobalVariables = proposalHeader.globalVariables; + // Kick off the process of collecting and validating proof quotes here so it runs alongside block building + const proofQuotePromise = this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt()).catch( + e => { + this.log.debug(`Failed to create proof claim quote ${e}`); + return undefined; + }, + ); + this.metrics.recordNewBlock(newGlobalVariables.blockNumber.toNumber(), validTxs.length); const workTimer = new Timer(); this.state = SequencerState.CREATING_BLOCK; @@ -488,8 +497,10 @@ export class Sequencer { const attestations = await this.collectAttestations(block, txHashes); this.log.verbose('Attestations collected'); + const proofQuote = await proofQuotePromise; + try { - await this.publishL2Block(block, attestations, txHashes); + await this.publishL2Block(block, attestations, txHashes, proofQuote); this.metrics.recordPublishedBlock(workDuration); this.log.info( `Submitted rollup block ${block.number} with ${ @@ -540,6 +551,37 @@ export class Sequencer { return orderAttestations(attestations, committee); } + protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise { + const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber); + if (epochForBlock < 1n) { + this.log.verbose(`First epoch has no claim`); + return undefined; + } + const epochToProve = epochForBlock - 1n; + const canClaim = await this.publisher.canClaimEpoch(epochToProve); + if (!canClaim) { + this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); + return undefined; + } + const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve); + this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`); + // ensure these quotes are still valid for the slot + const validQuotesPromise = Promise.all( + quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)), + ); + + const validQuotes = (await validQuotesPromise).filter((q): q is EpochProofQuote => !!q); + if (!validQuotes.length) { + this.log.verbose(`Failed to find any valid proof quotes`); + return undefined; + } + // pick the quote with the lowest fee + const sortedQuotes = validQuotes.sort( + (a: EpochProofQuote, b: EpochProofQuote) => a.payload.basisPointFee - b.payload.basisPointFee, + ); + return sortedQuotes[0]; + } + /** * Publishes the L2Block to the rollup contract. * @param block - The L2Block to be published. @@ -547,11 +589,16 @@ export class Sequencer { @trackSpan('Sequencer.publishL2Block', block => ({ [Attributes.BLOCK_NUMBER]: block.number, })) - protected async publishL2Block(block: L2Block, attestations?: Signature[], txHashes?: TxHash[]) { + protected async publishL2Block( + block: L2Block, + attestations?: Signature[], + txHashes?: TxHash[], + proofQuote?: EpochProofQuote, + ) { // Publishes new block to the network and awaits the tx to be mined this.state = SequencerState.PUBLISHING_BLOCK; - const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes); + const publishedL2Block = await this.publisher.proposeL2Block(block, attestations, txHashes, proofQuote); if (publishedL2Block) { this.lastPublishedBlock = block.number; } else { From 466e65b5d6878a201c81c83be311f00491266024 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:07:33 +0000 Subject: [PATCH 29/43] Merge fixes --- l1-contracts/src/core/interfaces/IRollup.sol | 2 +- l1-contracts/test/Rollup.t.sol | 34 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index b189bec92b7..5afb2c39c65 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -30,7 +30,7 @@ interface IRollup { function prune() external; - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) external; + function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external; function propose( bytes calldata _header, diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index e3fe3bd4b4a..64a6ca2e9a7 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -106,7 +106,7 @@ contract RollupTest is DecoderBase { // sanity check that proven/pending tip are at genesis vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(1); assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); @@ -114,7 +114,7 @@ contract RollupTest is DecoderBase { // empty slots do not move pending chain vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { @@ -127,7 +127,7 @@ contract RollupTest is DecoderBase { Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.quote.epochToProve ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { @@ -142,7 +142,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimPastValidUntil() public setUpFor("mixed_block_1") { @@ -153,7 +153,7 @@ contract RollupTest is DecoderBase { vm.expectRevert( abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.quote.validUntilSlot) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimSimple() public setUpFor("mixed_block_1") { @@ -163,7 +163,7 @@ contract RollupTest is DecoderBase { emit IRollup.ProofRightClaimed( quote.quote.epochToProve, address(0), address(this), quote.quote.bondAmount, Slot.wrap(1) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); ( Epoch epochToProve, @@ -184,14 +184,14 @@ contract RollupTest is DecoderBase { function testClaimTwice() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(2); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); // warp to epoch 1 warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); @@ -199,7 +199,7 @@ contract RollupTest is DecoderBase { // We should still be trying to prove epoch 0 in epoch 1 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); // still nothing to prune vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); @@ -218,7 +218,7 @@ contract RollupTest is DecoderBase { rollup.CLAIM_DURATION_IN_L2_SLOTS() ) ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { @@ -228,7 +228,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); @@ -243,18 +243,18 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); // We should still be trying to prove epoch 0 in epoch 2 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); rollup.prune(); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testClaimAfterPrune() public setUpFor("mixed_block_1") { @@ -265,7 +265,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 3); @@ -282,7 +282,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount, Epoch.wrap(3).toSlots() ); - rollup.claimEpochProofRight(quote); + rollup.claimEpochProofRight(quote.quote); } function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { From 0693ee646fbca8087b2ea00952bb7acda15f8dc4 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:28:19 +0000 Subject: [PATCH 30/43] More merge fixes --- l1-contracts/src/core/Rollup.sol | 14 ++++---- l1-contracts/src/core/interfaces/IRollup.sol | 12 +++++-- l1-contracts/test/Rollup.t.sol | 34 +++++++++---------- .../src/publisher/l1-publisher.ts | 13 ++----- .../src/sequencer/sequencer.test.ts | 26 ++++---------- .../src/sequencer/sequencer.ts | 4 +-- 6 files changed, 44 insertions(+), 59 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 2e8ae5f9673..e144742deaf 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -176,7 +176,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.EpochProofQuote calldata _quote + DataStructures.SignedEpochProofQuote calldata _quote ) external override(IRollup) { propose(_header, _archive, _blockHash, _txHashes, _signatures, _body); claimEpochProofRight(_quote); @@ -716,8 +716,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); } - if (_quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve); + if (_quote.quote.epochToProve != epochToProve) { + revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); } if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { @@ -732,14 +732,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Rollup__ProofRightAlreadyClaimed(); } - if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { + if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount + PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount ); } - if (_quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot); + if (_quote.quote.validUntilSlot < currentSlot) { + revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot); } } diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 5afb2c39c65..d9357e987ef 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -30,7 +30,7 @@ interface IRollup { function prune() external; - function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) external; + function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) external; function propose( bytes calldata _header, @@ -48,7 +48,7 @@ interface IRollup { bytes32[] memory _txHashes, SignatureLib.Signature[] memory _signatures, bytes calldata _body, - DataStructures.EpochProofQuote calldata _quote + DataStructures.SignedEpochProofQuote calldata _quote ) external; function submitBlockRootProof( @@ -116,8 +116,14 @@ interface IRollup { function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (Epoch); function nextEpochToClaim() external view returns (Epoch); - function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) external view; + function getEpochProofPublicInputs( + uint256 _epochSize, + bytes32[7] calldata _args, + bytes32[64] calldata _fees, + bytes calldata _aggregationObject + ) external view returns (bytes32[] memory); function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32); } diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 64a6ca2e9a7..e3fe3bd4b4a 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -106,7 +106,7 @@ contract RollupTest is DecoderBase { // sanity check that proven/pending tip are at genesis vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(1); assertEq(rollup.getCurrentSlot(), 1, "warp to slot 1 failed"); @@ -114,7 +114,7 @@ contract RollupTest is DecoderBase { // empty slots do not move pending chain vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { @@ -127,7 +127,7 @@ contract RollupTest is DecoderBase { Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, quote.quote.epochToProve ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { @@ -142,7 +142,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimPastValidUntil() public setUpFor("mixed_block_1") { @@ -153,7 +153,7 @@ contract RollupTest is DecoderBase { vm.expectRevert( abi.encodeWithSelector(Errors.Rollup__QuoteExpired.selector, 1, quote.quote.validUntilSlot) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimSimple() public setUpFor("mixed_block_1") { @@ -163,7 +163,7 @@ contract RollupTest is DecoderBase { emit IRollup.ProofRightClaimed( quote.quote.epochToProve, address(0), address(this), quote.quote.bondAmount, Slot.wrap(1) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); ( Epoch epochToProve, @@ -184,14 +184,14 @@ contract RollupTest is DecoderBase { function testClaimTwice() public setUpFor("mixed_block_1") { _testBlock("mixed_block_1", false, 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(2); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); // warp to epoch 1 warpToL2Slot(Constants.AZTEC_EPOCH_DURATION); @@ -199,7 +199,7 @@ contract RollupTest is DecoderBase { // We should still be trying to prove epoch 0 in epoch 1 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); // still nothing to prune vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); @@ -218,7 +218,7 @@ contract RollupTest is DecoderBase { rollup.CLAIM_DURATION_IN_L2_SLOTS() ) ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { @@ -228,7 +228,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS()); @@ -243,18 +243,18 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 2); // We should still be trying to prove epoch 0 in epoch 2 vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); rollup.prune(); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testClaimAfterPrune() public setUpFor("mixed_block_1") { @@ -265,7 +265,7 @@ contract RollupTest is DecoderBase { warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); warpToL2Slot(Constants.AZTEC_EPOCH_DURATION * 3); @@ -282,7 +282,7 @@ contract RollupTest is DecoderBase { quote.quote.bondAmount, Epoch.wrap(3).toSlots() ); - rollup.claimEpochProofRight(quote.quote); + rollup.claimEpochProofRight(quote); } function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 4c888a0bf0c..718d3f0b58a 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -185,16 +185,8 @@ export class L1Publisher { return [slot, blockNumber]; } - /** - * @notice Queries 'proofClaim' on the Rollup to determine if we are able to claim the given epoch for proving - * Only checks to see if the given epoch is claimed - * - * @param epochNumber - The epoch being queried - * @return True or False - */ - public async canClaimEpoch(epochNumber: bigint): Promise { - const nextToBeClaimed = await this.rollupContract.read.nextEpochToClaim(); - return epochNumber == nextToBeClaimed; + public async nextEpochToClaim(): Promise { + return await this.rollupContract.read.nextEpochToClaim(); } public async getEpochForSlotNumber(slotNumber: bigint): Promise { @@ -206,7 +198,6 @@ export class L1Publisher { try { await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account }); } catch (err) { - //console.log(err); return undefined; } return quote; diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 4c1d79c1540..e474abd2cc6 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -660,9 +660,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -686,9 +684,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -713,9 +709,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -739,7 +733,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockResolvedValue(false); + publisher.nextEpochToClaim.mockResolvedValue(currentEpoch); await sequencer.initialSync(); await sequencer.work(); @@ -765,9 +759,7 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation(_ => Promise.resolve(undefined)); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -823,9 +815,7 @@ describe('sequencer', () => { ); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); @@ -885,9 +875,7 @@ describe('sequencer', () => { ); // The previous epoch can be claimed - publisher.canClaimEpoch.mockImplementation((epochNumber: bigint) => - Promise.resolve(epochNumber === currentEpoch - 1n), - ); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); await sequencer.initialSync(); await sequencer.work(); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index f6a04198071..b799d176211 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -558,8 +558,8 @@ export class Sequencer { return undefined; } const epochToProve = epochForBlock - 1n; - const canClaim = await this.publisher.canClaimEpoch(epochToProve); - if (!canClaim) { + const canClaim = await this.publisher.nextEpochToClaim(); + if (canClaim != epochToProve) { this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); return undefined; } From 0a036e5627d97f4ca9e95ca6b5031e30f7a500dd Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:40:18 +0000 Subject: [PATCH 31/43] More merge fixes --- .../prover_coordination/epoch_proof_quote.test.ts | 2 +- .../src/prover_coordination/epoch_proof_quote.ts | 12 +++++++----- .../prover_coordination/epoch_proof_quote_payload.ts | 8 ++++---- .../p2p/src/epoch_proof_quote_pool/test_utils.ts | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts index c62995559ef..049845921e1 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts @@ -11,7 +11,7 @@ describe('epoch proof quote', () => { basisPointFee: 5000, bondAmount: 1000000000000000000n, epochToProve: 42n, - rollupAddress: EthAddress.random(), + prover: EthAddress.random(), validUntilSlot: 100n, }); diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts index 0e88a7ebffa..8839b257ff7 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts @@ -55,11 +55,13 @@ export class EpochProofQuote extends Gossipable { toViemArgs() { return { - epochToProve: this.payload.epochToProve, - validUntilSlot: this.payload.validUntilSlot, - bondAmount: this.payload.bondAmount, - rollupAddress: this.payload.rollupAddress, - basisPointFee: this.payload.basisPointFee, + quote: { + epochToProve: this.payload.epochToProve, + validUntilSlot: this.payload.validUntilSlot, + bondAmount: this.payload.bondAmount, + prover: this.payload.prover.toString(), + basisPointFee: this.payload.basisPointFee, + }, signature: this.signature.toViemSignature(), }; } diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts index bd0c92fcdc7..be70356f7d8 100644 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts +++ b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts @@ -11,7 +11,7 @@ export class EpochProofQuotePayload implements Signable { public readonly epochToProve: bigint, public readonly validUntilSlot: bigint, public readonly bondAmount: bigint, - public readonly rollupAddress: EthAddress, + public readonly prover: EthAddress, public readonly basisPointFee: number, ) {} @@ -20,7 +20,7 @@ export class EpochProofQuotePayload implements Signable { fields.epochToProve, fields.validUntilSlot, fields.bondAmount, - fields.rollupAddress, + fields.prover, fields.basisPointFee, ] as const; } @@ -45,7 +45,7 @@ export class EpochProofQuotePayload implements Signable { fields.epochToProve, fields.validUntilSlot, fields.bondAmount, - fields.rollupAddress, + fields.prover, fields.basisPointFee, ); } @@ -56,7 +56,7 @@ export class EpochProofQuotePayload implements Signable { this.epochToProve, this.validUntilSlot, this.bondAmount, - this.rollupAddress.toString(), + this.prover.toString(), this.basisPointFee, ] as const); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts index a12e10d85fa..0847e254014 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/test_utils.ts @@ -7,7 +7,7 @@ export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { basisPointFee: randomInt(10000), bondAmount: 1000000000000000000n, epochToProve: randomBigInt(1000000n), - rollupAddress: EthAddress.random(), + prover: EthAddress.random(), validUntilSlot: randomBigInt(1000000n), }); } From 2e698c7ac9c202675f5f763874a4382e0d329262 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 20:58:32 +0000 Subject: [PATCH 32/43] Refactoring --- .../src/publisher/l1-publisher.ts | 100 +++++++----------- 1 file changed, 40 insertions(+), 60 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 718d3f0b58a..475f73c5432 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -513,40 +513,46 @@ export class L1Publisher { } } + private async prepareProposeTx(encodedData: L1ProcessArgs, gasGuess: bigint) { + // We have to jump a few hoops because viem is not happy around estimating gas for view functions + const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ + to: this.rollupContract.address, + data: encodeFunctionData({ + abi: this.rollupContract.abi, + functionName: 'computeTxsEffectsHash', + args: [`0x${encodedData.body.toString('hex')}`], + }), + }); + + // @note We perform this guesstimate instead of the usual `gasEstimate` since + // viem will use the current state to simulate against, which means that + // we will fail estimation in the case where we are simulating for the + // first ethereum block within our slot (as current time is not in the + // slot yet). + const gasGuesstimate = computeTxsEffectsHashGas + gasGuess; + + const attestations = encodedData.attestations + ? encodedData.attestations.map(attest => attest.toViemSignature()) + : []; + const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; + const args = [ + `0x${encodedData.header.toString('hex')}`, + `0x${encodedData.archive.toString('hex')}`, + `0x${encodedData.blockHash.toString('hex')}`, + txHashes, + attestations, + `0x${encodedData.body.toString('hex')}`, + ] as const; + + return { args, gasGuesstimate }; + } + private async sendProposeTx(encodedData: L1ProcessArgs): Promise { if (this.interrupted) { return; } try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); - - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - ] as const; + const { args, gasGuesstimate } = await this.prepareProposeTx(encodedData, L1Publisher.PROPOSE_GAS_GUESS); return await this.rollupContract.write.propose(args, { account: this.account, @@ -564,38 +570,12 @@ export class L1Publisher { return; } try { - // We have to jump a few hoops because viem is not happy around estimating gas for view functions - const computeTxsEffectsHashGas = await this.publicClient.estimateGas({ - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: this.rollupContract.abi, - functionName: 'computeTxsEffectsHash', - args: [`0x${encodedData.body.toString('hex')}`], - }), - }); - - // @note We perform this guesstimate instead of the usual `gasEstimate` since - // viem will use the current state to simulate against, which means that - // we will fail estimation in the case where we are simulating for the - // first ethereum block within our slot (as current time is not in the - // slot yet). - const gasGuesstimate = computeTxsEffectsHashGas + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS; - - const attestations = encodedData.attestations - ? encodedData.attestations.map(attest => attest.toViemSignature()) - : []; - const txHashes = encodedData.txHashes ? encodedData.txHashes.map(txHash => txHash.to0xString()) : []; - const args = [ - `0x${encodedData.header.toString('hex')}`, - `0x${encodedData.archive.toString('hex')}`, - `0x${encodedData.blockHash.toString('hex')}`, - txHashes, - attestations, - `0x${encodedData.body.toString('hex')}`, - quote.toViemArgs(), - ] as const; + const { args, gasGuesstimate } = await this.prepareProposeTx( + encodedData, + L1Publisher.PROPOSE_AND_CLAIM_GAS_GUESS, + ); - return await this.rollupContract.write.proposeAndClaim(args, { + return await this.rollupContract.write.proposeAndClaim([...args, quote.toViemArgs()], { account: this.account, gas: gasGuesstimate, }); From 45783844f38fd190d7c9bc84dec097281cf28745 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Thu, 26 Sep 2024 21:12:14 +0000 Subject: [PATCH 33/43] Comments --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index b799d176211..eb1ac9b3b26 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -552,20 +552,25 @@ export class Sequencer { } protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise { + // Find out which epoch we are currently in const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber); if (epochForBlock < 1n) { + // It's the 0th epoch, nothing to be proven yet this.log.verbose(`First epoch has no claim`); return undefined; } const epochToProve = epochForBlock - 1n; + // Find out the next epoch that can be claimed const canClaim = await this.publisher.nextEpochToClaim(); if (canClaim != epochToProve) { + // It's not the one we are looking to claim this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); return undefined; } + // Get quotes for the epoch to be proven const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve); this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`); - // ensure these quotes are still valid for the slot + // ensure these quotes are still valid for the slot and have the contract validate them const validQuotesPromise = Promise.all( quotes.filter(x => x.payload.validUntilSlot >= slotNumber).map(x => this.publisher.validateProofQuote(x)), ); From 3eb5f9be9fbd93d91367d3a0abe79bc95112f6bc Mon Sep 17 00:00:00 2001 From: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:24:47 +0100 Subject: [PATCH 34/43] Update yarn-project/sequencer-client/src/sequencer/sequencer.ts Co-authored-by: Santiago Palladino --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index eb1ac9b3b26..6b5cf45ba47 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -564,7 +564,7 @@ export class Sequencer { const canClaim = await this.publisher.nextEpochToClaim(); if (canClaim != epochToProve) { // It's not the one we are looking to claim - this.log.verbose(`Unable to claim previous epoch (${epochToProve})`); + this.log.verbose(`Unable to claim previous epoch (${canClaim} != ${epochToProve})`); return undefined; } // Get quotes for the epoch to be proven From 6bcc3a07e410171e03d3713aaadf06d3acf76914 Mon Sep 17 00:00:00 2001 From: PhilWindle <60546371+PhilWindle@users.noreply.github.com> Date: Fri, 27 Sep 2024 09:25:00 +0100 Subject: [PATCH 35/43] Update yarn-project/sequencer-client/src/sequencer/sequencer.ts Co-authored-by: Santiago Palladino --- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 6b5cf45ba47..6113a8e9814 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -408,7 +408,7 @@ export class Sequencer { // Kick off the process of collecting and validating proof quotes here so it runs alongside block building const proofQuotePromise = this.createProofClaimForPreviousEpoch(newGlobalVariables.slotNumber.toBigInt()).catch( e => { - this.log.debug(`Failed to create proof claim quote ${e}`); + this.log.warn(`Failed to create proof claim quote ${e}`); return undefined; }, ); From 8274b4a02ec56d066ffeadf1cddb052690955419 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 08:34:03 +0000 Subject: [PATCH 36/43] Review changes --- yarn-project/sequencer-client/src/publisher/l1-publisher.ts | 6 +++--- yarn-project/sequencer-client/src/publisher/utils.ts | 2 +- yarn-project/sequencer-client/src/sequencer/sequencer.ts | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index 475f73c5432..ee1ed2db56b 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -40,7 +40,7 @@ import type * as chains from 'viem/chains'; import { type PublisherConfig, type TxSenderConfig } from './config.js'; import { L1PublisherMetrics } from './l1-publisher-metrics.js'; -import { prettyLogVeimError } from './utils.js'; +import { prettyLogViemError } from './utils.js'; /** * Stats for a sent transaction. @@ -559,7 +559,7 @@ export class L1Publisher { gas: gasGuesstimate, }); } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); this.log.error(`Rollup publish failed`, err); return undefined; } @@ -580,7 +580,7 @@ export class L1Publisher { gas: gasGuesstimate, }); } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); this.log.error(`Rollup publish failed`, err); return undefined; } diff --git a/yarn-project/sequencer-client/src/publisher/utils.ts b/yarn-project/sequencer-client/src/publisher/utils.ts index 13842102a2c..8889aa0998c 100644 --- a/yarn-project/sequencer-client/src/publisher/utils.ts +++ b/yarn-project/sequencer-client/src/publisher/utils.ts @@ -2,7 +2,7 @@ import { type Logger } from '@aztec/foundation/log'; import { BaseError, ContractFunctionRevertedError } from 'viem'; -export function prettyLogVeimError(err: any, logger: Logger) { +export function prettyLogViemError(err: any, logger: Logger) { if (err instanceof BaseError) { const revertError = err.walk(err => err instanceof ContractFunctionRevertedError); if (revertError instanceof ContractFunctionRevertedError) { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 6113a8e9814..5e7bd9a4ae3 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -35,7 +35,7 @@ import { type ValidatorClient } from '@aztec/validator-client'; import { type BlockBuilderFactory } from '../block_builder/index.js'; import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; import { type L1Publisher } from '../publisher/l1-publisher.js'; -import { prettyLogVeimError } from '../publisher/utils.js'; +import { prettyLogViemError } from '../publisher/utils.js'; import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js'; import { type SequencerConfig } from './config.js'; import { SequencerMetrics } from './metrics.js'; @@ -312,7 +312,7 @@ export class Sequencer { this.log.debug(`Can propose block ${proposalBlockNumber} at slot ${slot}`); return slot; } catch (err) { - prettyLogVeimError(err, this.log); + prettyLogViemError(err, this.log); throw err; } } From 900c8e253dad6b36f2f72e3f33fdff2895de4eea Mon Sep 17 00:00:00 2001 From: Mitch Date: Fri, 27 Sep 2024 07:10:33 -0400 Subject: [PATCH 37/43] update conflicts --- l1-contracts/src/core/Leonidas.sol | 4 +- l1-contracts/src/core/Rollup.sol | 115 ++++-------------- .../src/core/libraries/DataStructures.sol | 2 - l1-contracts/test/sparta/Sparta.t.sol | 6 - 4 files changed, 24 insertions(+), 103 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index bb6a69dd630..d243d6eed88 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -327,8 +327,8 @@ contract Leonidas is Ownable, ILeonidas { * * @return The computed epoch */ - function getEpochAtSlot(uint256 _slotNumber) public pure override(ILeonidas) returns (uint256) { - return _slotNumber / EPOCH_DURATION; + function getEpochAtSlot(Slot _slotNumber) public pure override(ILeonidas) returns (Epoch) { + return Epoch.wrap(_slotNumber.unwrap() / EPOCH_DURATION); } /** diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index e144742deaf..285509c89da 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -587,12 +587,12 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags); } - function nextEpochToClaim() external view override(IRollup) returns (uint256) { - uint256 epochClaimed = proofClaim.epochToProve; - if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) { - return 0; + function nextEpochToClaim() external view override(IRollup) returns (Epoch) { + Epoch epochClaimed = proofClaim.epochToProve; + if (proofClaim.proposerClaimant == address(0) && epochClaimed == Epoch.wrap(0)) { + return Epoch.wrap(0); } - return 1 + epochClaimed; + return Epoch.wrap(1) + epochClaimed; } function computeTxsEffectsHash(bytes calldata _body) @@ -604,29 +604,31 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return TxsDecoder.decode(_body); } - function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote) + function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) public override(IRollup) { validateEpochProofRightClaim(_quote); - uint256 currentSlot = getCurrentSlot(); - uint256 epochToProve = getEpochToProve(); + Slot currentSlot = getCurrentSlot(); + Epoch epochToProve = getEpochToProve(); // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. // Blocked on submitting epoch proofs to this contract. - address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount); + PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); proofClaim = DataStructures.EpochProofClaim({ epochToProve: epochToProve, - basisPointFee: _quote.basisPointFee, - bondAmount: _quote.bondAmount, - bondProvider: bondProvider, + basisPointFee: _quote.quote.basisPointFee, + bondAmount: _quote.quote.bondAmount, + bondProvider: _quote.quote.prover, proposerClaimant: msg.sender }); - emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot); + emit ProofRightClaimed( + epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot + ); } /** @@ -661,7 +663,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _header: header, _signatures: _signatures, _digest: digest, - _currentTime: block.timestamp, + _currentTime: Timestamp.wrap(block.timestamp), _txEffectsHash: txsEffectsHash, _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) }); @@ -671,7 +673,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { blocks[blockNumber] = BlockLog({ archive: _archive, blockHash: _blockHash, - slotNumber: header.globalVariables.slotNumber.toUint128() + slotNumber: Slot.wrap(header.globalVariables.slotNumber) }); // @note The block number here will always be >=1 as the genesis block is at 0 @@ -703,14 +705,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } } - function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote) + function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) public view override(IRollup) { - uint256 currentSlot = getCurrentSlot(); + Slot currentSlot = getCurrentSlot(); address currentProposer = getCurrentProposer(); - uint256 epochToProve = getEpochToProve(); + Epoch epochToProve = getEpochToProve(); if (currentProposer != address(0) && currentProposer != msg.sender) { revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); @@ -720,9 +722,9 @@ contract Rollup is Leonidas, IRollup, ITestRollup { revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); } - if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { + if (currentSlot.positionInEpoch() >= CLAIM_DURATION_IN_L2_SLOTS) { revert Errors.Rollup__NotInClaimPhase( - currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS + currentSlot.positionInEpoch(), CLAIM_DURATION_IN_L2_SLOTS ); } @@ -760,79 +762,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } - /** - * @notice Get the epoch that should be proven - * - * @dev This is the epoch that should be proven. It does so by getting the epoch of the block - * following the last proven block. If there is no such block (i.e. the pending chain is - * the same as the proven chain), then revert. - * - * @return uint256 - The epoch to prove - */ - function getEpochToProve() public view override(IRollup) returns (uint256) { - if (tips.provenBlockNumber == tips.pendingBlockNumber) { - revert Errors.Rollup__NoEpochToProve(); - } else { - return getEpochAt(getTimestampForSlot(blocks[getProvenBlockNumber() + 1].slotNumber)); - } - } - - /** - * @notice Get the archive root of a specific block - * - * @param _blockNumber - The block number to get the archive root of - * - * @return bytes32 - The archive root of the block - */ - function archiveAt(uint256 _blockNumber) public view override(IRollup) returns (bytes32) { - if (_blockNumber <= tips.pendingBlockNumber) { - return blocks[_blockNumber].archive; - } - return bytes32(0); - } - - function _prune() internal { - // TODO #8656 - delete proofClaim; - - uint256 pending = tips.pendingBlockNumber; - - // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. - // We can do because any new block proposed will overwrite a previous block in the block log, - // so no values should "survive". - // People must therefore read the chain using the pendingTip as a boundary. - tips.pendingBlockNumber = tips.provenBlockNumber; - - emit PrunedPending(tips.provenBlockNumber, pending); - } - - function _canPrune() internal view returns (bool) { - if (tips.pendingBlockNumber == tips.provenBlockNumber) { - return false; - } - - uint256 currentSlot = getCurrentSlot(); - uint256 oldestPendingEpoch = - getEpochAt(getTimestampForSlot(blocks[tips.provenBlockNumber + 1].slotNumber)); - uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; - - // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. - // we prune the pending chain back to the end of epoch 1 if: - // - the proof claim phase of epoch 3 has ended without a claim to prove epoch 2 (or proof of epoch 2) - // - we reach epoch 4 without a proof of epoch 2 (regardless of whether a proof claim was submitted) - bool inClaimPhase = currentSlot - < startSlotOfPendingEpoch + Constants.AZTEC_EPOCH_DURATION + CLAIM_DURATION_IN_L2_SLOTS; - - bool claimExists = currentSlot < startSlotOfPendingEpoch + 2 * Constants.AZTEC_EPOCH_DURATION - && proofClaim.epochToProve == oldestPendingEpoch && proofClaim.proposerClaimant != address(0); - - if (inClaimPhase || claimExists) { - // If we are in the claim phase, do not prune - return false; - } - return true; - } - /** * @notice Get the epoch that should be proven * diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 6e5ecb85e59..13911e4d7c2 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -6,8 +6,6 @@ import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {Slot, Epoch} from "@aztec/core/libraries/TimeMath.sol"; -import {SignatureLib} from "./SignatureLib.sol"; - /** * @title Data Structures Library * @author Aztec Labs diff --git a/l1-contracts/test/sparta/Sparta.t.sol b/l1-contracts/test/sparta/Sparta.t.sol index 76516ab4090..1dfd393df44 100644 --- a/l1-contracts/test/sparta/Sparta.t.sol +++ b/l1-contracts/test/sparta/Sparta.t.sol @@ -40,12 +40,6 @@ contract SpartaTest is DecoderBase { bool shouldRevert; } - struct StructToAvoidDeepStacks { - uint256 needed; - address proposer; - bool shouldRevert; - } - Registry internal registry; Inbox internal inbox; Outbox internal outbox; From 28ba9cb8a437eac16b797ef42ea2293c5f5f9af7 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 11:27:24 +0000 Subject: [PATCH 38/43] Delete expired epoch proofs --- l1-contracts/src/core/Rollup.sol | 20 ++++-- l1-contracts/src/core/interfaces/IRollup.sol | 4 +- .../archiver/src/archiver/archiver.ts | 23 ++++++- .../archiver/src/archiver/archiver_store.ts | 12 ++++ .../archiver/kv_archiver_store/block_store.ts | 12 ++++ .../kv_archiver_store/kv_archiver_store.ts | 9 +++ .../memory_archiver_store.ts | 10 +++ .../circuit-types/src/l2_block_source.ts | 6 ++ yarn-project/circuit-types/src/mocks.ts | 20 +++--- yarn-project/p2p/src/client/mocks.ts | 9 +++ .../p2p/src/client/p2p_client.test.ts | 62 ++++++++++++++++++- .../epoch_proof_quote_pool.ts | 1 + .../memory_epoch_proof_quote_pool.test.ts | 38 +++++++++++- .../memory_epoch_proof_quote_pool.ts | 10 ++- .../reqresp/p2p_client.integration.test.ts | 1 + 15 files changed, 213 insertions(+), 24 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index bade58dec0a..3be4df41a91 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -381,7 +381,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes32 provenArchive, uint256 pendingBlockNumber, bytes32 pendingArchive, - bytes32 archiveOfMyBlock + bytes32 archiveOfMyBlock, + uint256 provenEpochNumber ) { return ( @@ -389,7 +390,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { blocks[tips.provenBlockNumber].archive, tips.pendingBlockNumber, blocks[tips.pendingBlockNumber].archive, - archiveAt(myHeaderBlockNumber) + archiveAt(myHeaderBlockNumber), + getEpochForBlock(tips.provenBlockNumber) ); } @@ -757,6 +759,15 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } + function getEpochForBlock(uint256 blockNumber) public view override(IRollup) returns (uint256) { + if (blockNumber > tips.pendingBlockNumber) { + revert Errors.Rollup__InvalidBlockNumber( + tips.pendingBlockNumber, blockNumber + ); + } + return getEpochAt(getTimestampForSlot(blocks[blockNumber].slotNumber)); + } + /** * @notice Get the epoch that should be proven * @@ -770,7 +781,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { if (tips.provenBlockNumber == tips.pendingBlockNumber) { revert Errors.Rollup__NoEpochToProve(); } else { - return getEpochAt(getTimestampForSlot(blocks[getProvenBlockNumber() + 1].slotNumber)); + return getEpochForBlock(getProvenBlockNumber() + 1); } } @@ -812,8 +823,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } uint256 currentSlot = getCurrentSlot(); - uint256 oldestPendingEpoch = - getEpochAt(getTimestampForSlot(blocks[tips.provenBlockNumber + 1].slotNumber)); + uint256 oldestPendingEpoch = getEpochForBlock(tips.provenBlockNumber + 1); uint256 startSlotOfPendingEpoch = oldestPendingEpoch * Constants.AZTEC_EPOCH_DURATION; // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 141e319cc2c..a6858c50aa3 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -93,7 +93,8 @@ interface IRollup { bytes32 provenArchive, uint256 pendingBlockCount, bytes32 pendingArchive, - bytes32 archiveOfMyBlock + bytes32 archiveOfMyBlock, + uint256 provenEpochNumber ); // TODO(#7346): Integrate batch rollups @@ -114,6 +115,7 @@ interface IRollup { function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (uint256); function nextEpochToClaim() external view returns (uint256); + function getEpochForBlock(uint256 blockNumber) external view returns (uint256); function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) external view; diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index ce8994dff5b..1b43d897eea 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -278,8 +278,14 @@ export class Archiver implements ArchiveSource { } const localPendingBlockNumber = BigInt(await this.getBlockNumber()); - const [provenBlockNumber, provenArchive, pendingBlockNumber, pendingArchive, archiveForLocalPendingBlockNumber] = - await this.rollup.read.status([localPendingBlockNumber]); + const [ + provenBlockNumber, + provenArchive, + pendingBlockNumber, + pendingArchive, + archiveForLocalPendingBlockNumber, + provenEpochNumber, + ] = await this.rollup.read.status([localPendingBlockNumber]); const updateProvenBlock = async () => { const localBlockForDestinationProvenBlockNumber = await this.getBlock(Number(provenBlockNumber)); @@ -287,8 +293,9 @@ export class Archiver implements ArchiveSource { localBlockForDestinationProvenBlockNumber && provenArchive === localBlockForDestinationProvenBlockNumber.archive.root.toString() ) { - this.log.info(`Updating the proven block number to ${provenBlockNumber}`); + this.log.info(`Updating the proven block number to ${provenBlockNumber} and epoch to ${provenEpochNumber}`); await this.store.setProvenL2BlockNumber(Number(provenBlockNumber)); + await this.store.setProvenL2EpochNumber(Number(provenEpochNumber)); } }; @@ -509,6 +516,10 @@ export class Archiver implements ArchiveSource { return this.store.getProvenL2BlockNumber(); } + public getProvenL2EpochNumber(): Promise { + return this.store.getProvenL2EpochNumber(); + } + /** Forcefully updates the last proven block number. Use for testing. */ public setProvenBlockNumber(blockNumber: number): Promise { return this.store.setProvenL2BlockNumber(blockNumber); @@ -756,9 +767,15 @@ class ArchiverStoreHelper getProvenL2BlockNumber(): Promise { return this.store.getProvenL2BlockNumber(); } + getProvenL2EpochNumber(): Promise { + return this.store.getProvenL2EpochNumber(); + } setProvenL2BlockNumber(l2BlockNumber: number): Promise { return this.store.setProvenL2BlockNumber(l2BlockNumber); } + setProvenL2EpochNumber(l2EpochNumber: number): Promise { + return this.store.setProvenL2EpochNumber(l2EpochNumber); + } setBlockSynchedL1BlockNumber(l1BlockNumber: bigint): Promise { return this.store.setBlockSynchedL1BlockNumber(l1BlockNumber); } diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index 2218be901d4..a04da4f524f 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -146,12 +146,24 @@ export interface ArchiverDataStore { */ getProvenL2BlockNumber(): Promise; + /** + * Gets the number of the latest proven L2 epoch. + * @returns The number of the latest proven L2 epoch. + */ + getProvenL2EpochNumber(): Promise; + /** * Stores the number of the latest proven L2 block processed. * @param l2BlockNumber - The number of the latest proven L2 block processed. */ setProvenL2BlockNumber(l2BlockNumber: number): Promise; + /** + * Stores the number of the latest proven L2 epoch. + * @param l2EpochNumber - The number of the latest proven L2 epoch. + */ + setProvenL2EpochNumber(l2EpochNumber: number): Promise; + /** * Stores the l1 block number that blocks have been synched until * @param l1BlockNumber - The l1 block number diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts index a3d91989980..f1383a09e74 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts @@ -29,6 +29,9 @@ export class BlockStore { /** Stores l2 block number of the last proven block */ #lastProvenL2Block: AztecSingleton; + /** Stores l2 epoch number of the last proven epoch */ + #lastProvenL2Epoch: AztecSingleton; + /** Index mapping transaction hash (as a string) to its location in a block */ #txIndex: AztecMap; @@ -44,6 +47,7 @@ export class BlockStore { this.#contractIndex = db.openMap('archiver_contract_index'); this.#lastSynchedL1Block = db.openSingleton('archiver_last_synched_l1_block'); this.#lastProvenL2Block = db.openSingleton('archiver_last_proven_l2_block'); + this.#lastProvenL2Epoch = db.openSingleton('archiver_last_proven_l2_epoch'); } /** @@ -235,6 +239,14 @@ export class BlockStore { void this.#lastProvenL2Block.set(blockNumber); } + getProvenL2EpochNumber(): number { + return this.#lastProvenL2Epoch.get() ?? 0; + } + + setProvenL2EpochNumber(epochNumber: number) { + void this.#lastProvenL2Epoch.set(epochNumber); + } + #computeBlockRange(start: number, limit: number): Required, 'start' | 'end'>> { if (limit < 1) { throw new Error(`Invalid limit: ${limit}`); diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index 7544fd0941a..25a3394deae 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -247,11 +247,20 @@ export class KVArchiverDataStore implements ArchiverDataStore { return Promise.resolve(this.#blockStore.getProvenL2BlockNumber()); } + getProvenL2EpochNumber(): Promise { + return Promise.resolve(this.#blockStore.getProvenL2EpochNumber()); + } + setProvenL2BlockNumber(blockNumber: number) { this.#blockStore.setProvenL2BlockNumber(blockNumber); return Promise.resolve(); } + setProvenL2EpochNumber(epochNumber: number) { + this.#blockStore.setProvenL2EpochNumber(epochNumber); + return Promise.resolve(); + } + setBlockSynchedL1BlockNumber(l1BlockNumber: bigint) { this.#blockStore.setSynchedL1BlockNumber(l1BlockNumber); return Promise.resolve(); diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index 9a1fe11ff58..fd0b1f48cb4 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -70,6 +70,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { private lastL1BlockNewMessages: bigint | undefined = undefined; private lastProvenL2BlockNumber: number = 0; + private lastProvenL2EpochNumber: number = 0; constructor( /** The max number of logs that can be obtained in 1 "getUnencryptedLogs" call. */ @@ -471,11 +472,20 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(this.lastProvenL2BlockNumber); } + public getProvenL2EpochNumber(): Promise { + return Promise.resolve(this.lastProvenL2EpochNumber); + } + public setProvenL2BlockNumber(l2BlockNumber: number): Promise { this.lastProvenL2BlockNumber = l2BlockNumber; return Promise.resolve(); } + public setProvenL2EpochNumber(l2EpochNumber: number): Promise { + this.lastProvenL2EpochNumber = l2EpochNumber; + return Promise.resolve(); + } + setBlockSynchedL1BlockNumber(l1BlockNumber: bigint) { this.lastL1BlockNewBlocks = l1BlockNumber; return Promise.resolve(); diff --git a/yarn-project/circuit-types/src/l2_block_source.ts b/yarn-project/circuit-types/src/l2_block_source.ts index 45727f2156f..d8ca2dde2eb 100644 --- a/yarn-project/circuit-types/src/l2_block_source.ts +++ b/yarn-project/circuit-types/src/l2_block_source.ts @@ -33,6 +33,12 @@ export interface L2BlockSource { */ getProvenBlockNumber(): Promise; + /** + * Gets the number of the latest L2 proven epoch seen by the block source implementation. + * @returns The number of the latest L2 proven epoch seen by the block source implementation. + */ + getProvenL2EpochNumber(): Promise; + /** * Gets an l2 block. If a negative number is passed, the block returned is the most recent. * @param number - The block number to return (inclusive). diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index f594a081ab2..5c86312a170 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -2,7 +2,7 @@ import { AztecAddress, CallContext, ClientIvcProof, - type EthAddress, + EthAddress, GasSettings, LogHash, MAX_ENCRYPTED_LOGS_PER_TX, @@ -28,7 +28,7 @@ import { import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { padArrayEnd, times } from '@aztec/foundation/collection'; -import { randomBytes } from '@aztec/foundation/crypto'; +import { randomBigInt, randomBytes, randomInt } from '@aztec/foundation/crypto'; import { Signature } from '@aztec/foundation/eth-signature'; import { Fr } from '@aztec/foundation/fields'; import { type ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts'; @@ -229,17 +229,17 @@ export const mockSimulatedTx = (seed = 1, hasLogs = true) => { export const mockEpochProofQuote = ( epochToProve: bigint, - validUntilSlot: bigint, - bondAmount: bigint, - rollupAddress: EthAddress, - basisPointFee: number, + validUntilSlot?: bigint, + bondAmount?: bigint, + proverAddress?: EthAddress, + basisPointFee?: number, ) => { const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( epochToProve, - validUntilSlot, - bondAmount, - rollupAddress, - basisPointFee, + validUntilSlot ?? randomBigInt(10000n), + bondAmount ?? randomBigInt(10000n) + 1000n, + proverAddress ?? EthAddress.random(), + basisPointFee ?? randomInt(100), ); const sig: Signature = Signature.empty(); return new EpochProofQuote(quotePayload, sig); diff --git a/yarn-project/p2p/src/client/mocks.ts b/yarn-project/p2p/src/client/mocks.ts index 599769a9d30..2d216676a20 100644 --- a/yarn-project/p2p/src/client/mocks.ts +++ b/yarn-project/p2p/src/client/mocks.ts @@ -7,6 +7,7 @@ import { EthAddress } from '@aztec/circuits.js'; export class MockBlockSource implements L2BlockSource { private l2Blocks: L2Block[] = []; private txEffects: TxEffect[] = []; + private provenEpochNumber = 0; constructor(numBlocks = 100, private provenBlockNumber?: number) { this.addBlocks(numBlocks); @@ -25,6 +26,10 @@ export class MockBlockSource implements L2BlockSource { this.provenBlockNumber = provenBlockNumber; } + public setProvenEpochNumber(provenEpochNumber: number) { + this.provenEpochNumber = provenEpochNumber; + } + /** * Method to fetch the rollup contract address at the base-layer. * @returns The rollup address. @@ -53,6 +58,10 @@ export class MockBlockSource implements L2BlockSource { return this.provenBlockNumber ?? (await this.getBlockNumber()); } + public getProvenL2EpochNumber(): Promise { + return Promise.resolve(this.provenEpochNumber); + } + /** * Gets an l2 block. * @param number - The block number to return (inclusive). diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 98164007f05..4ce03c3b698 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -1,4 +1,4 @@ -import { mockTx } from '@aztec/circuit-types'; +import { mockEpochProofQuote, mockTx } from '@aztec/circuit-types'; import { retryUntil } from '@aztec/foundation/retry'; import { type AztecKVStore } from '@aztec/kv-store'; import { openTmpStore } from '@aztec/kv-store/utils'; @@ -59,6 +59,7 @@ describe('In-Memory P2P Client', () => { epochProofQuotePool = { addQuote: jest.fn(), getQuotes: jest.fn().mockReturnValue([]), + deleteQuotesToEpoch: jest.fn(), }; blockSource = new MockBlockSource(); @@ -67,8 +68,9 @@ describe('In-Memory P2P Client', () => { client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); }); - const advanceToProvenBlock = async (getProvenBlockNumber: number) => { + const advanceToProvenBlock = async (getProvenBlockNumber: number, provenEpochNumber = getProvenBlockNumber) => { blockSource.setProvenBlockNumber(getProvenBlockNumber); + blockSource.setProvenEpochNumber(provenEpochNumber); await retryUntil( () => Promise.resolve(client.getSyncedProvenBlockNum() >= getProvenBlockNumber), 'synced', @@ -158,5 +160,61 @@ describe('In-Memory P2P Client', () => { await client.stop(); }); + it('stores and returns epoch proof quotes', async () => { + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); + + blockSource.setProvenEpochNumber(2); + await client.start(); + + const proofQuotes = [ + mockEpochProofQuote(3n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + mockEpochProofQuote(4n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + ]; + + for (const quote of proofQuotes) { + client.broadcastEpochProofQuote(quote); + } + expect(epochProofQuotePool.addQuote).toBeCalledTimes(proofQuotes.length); + + for (let i = 0; i < proofQuotes.length; i++) { + expect(epochProofQuotePool.addQuote).toHaveBeenNthCalledWith(i + 1, proofQuotes[i]); + } + expect(epochProofQuotePool.addQuote).toBeCalledTimes(proofQuotes.length); + + await client.getEpochProofQuotes(2n); + + expect(epochProofQuotePool.getQuotes).toBeCalledTimes(1); + expect(epochProofQuotePool.getQuotes).toBeCalledWith(2n); + }); + + it('deletes expired proof quotes', async () => { + client = new P2PClient(kvStore, blockSource, txPool, attestationPool, epochProofQuotePool, p2pService, 0); + + blockSource.setProvenEpochNumber(1); + blockSource.setProvenBlockNumber(1); + await client.start(); + + const proofQuotes = [ + mockEpochProofQuote(3n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + mockEpochProofQuote(4n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + ]; + + for (const quote of proofQuotes) { + client.broadcastEpochProofQuote(quote); + } + + await advanceToProvenBlock(3, 3); + + expect(epochProofQuotePool.deleteQuotesToEpoch).toBeCalledWith(3n); + }); + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/7971): tests for attestation pool pruning }); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts index 6d9067c76d2..94776d04ac3 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/epoch_proof_quote_pool.ts @@ -3,4 +3,5 @@ import { type EpochProofQuote } from '@aztec/circuit-types'; export interface EpochProofQuotePool { addQuote(quote: EpochProofQuote): void; getQuotes(epoch: bigint): EpochProofQuote[]; + deleteQuotesToEpoch(epoch: bigint): void; } diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts index 35c8611006f..16ea4aeec52 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts @@ -1,5 +1,6 @@ +import { mockEpochProofQuote } from '@aztec/circuit-types'; + import { MemoryEpochProofQuotePool } from './memory_epoch_proof_quote_pool.js'; -import { makeRandomEpochProofQuote } from './test_utils.js'; describe('MemoryEpochProofQuotePool', () => { let pool: MemoryEpochProofQuotePool; @@ -9,7 +10,7 @@ describe('MemoryEpochProofQuotePool', () => { }); it('should add/get quotes to/from pool', () => { - const { quote } = makeRandomEpochProofQuote(); + const quote = mockEpochProofQuote(5n); pool.addQuote(quote); @@ -18,4 +19,37 @@ describe('MemoryEpochProofQuotePool', () => { expect(quotes).toHaveLength(1); expect(quotes[0]).toEqual(quote); }); + + it('should delete quotes for expired epochs', () => { + const proofQuotes = [ + mockEpochProofQuote(3n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + mockEpochProofQuote(4n), + mockEpochProofQuote(2n), + mockEpochProofQuote(3n), + ]; + + for (const quote of proofQuotes) { + pool.addQuote(quote); + } + + const quotes3 = pool.getQuotes(3n); + const quotesForEpoch3 = proofQuotes.filter(x => x.payload.epochToProve === 3n); + + expect(quotes3).toHaveLength(quotesForEpoch3.length); + expect(quotes3).toEqual(quotesForEpoch3); + + // should delete all quotes for epochs 2 and 3 + pool.deleteQuotesToEpoch(3n); + + expect(pool.getQuotes(2n)).toHaveLength(0); + expect(pool.getQuotes(3n)).toHaveLength(0); + + const quotes4 = pool.getQuotes(4n); + const quotesForEpoch4 = proofQuotes.filter(x => x.payload.epochToProve === 4n); + + expect(quotes4).toHaveLength(quotesForEpoch4.length); + expect(quotes4).toEqual(quotesForEpoch4); + }); }); diff --git a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts index 16b178c92fe..a9166838a1b 100644 --- a/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts +++ b/yarn-project/p2p/src/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts @@ -1,6 +1,8 @@ import { type EpochProofQuote } from '@aztec/circuit-types'; -export class MemoryEpochProofQuotePool { +import { type EpochProofQuotePool } from './epoch_proof_quote_pool.js'; + +export class MemoryEpochProofQuotePool implements EpochProofQuotePool { private quotes: Map; constructor() { this.quotes = new Map(); @@ -15,4 +17,10 @@ export class MemoryEpochProofQuotePool { getQuotes(epoch: bigint): EpochProofQuote[] { return this.quotes.get(epoch) || []; } + deleteQuotesToEpoch(epoch: bigint): void { + const expiredEpochs = Array.from(this.quotes.keys()).filter(k => k <= epoch); + for (const expiredEpoch of expiredEpochs) { + this.quotes.delete(expiredEpoch); + } + } } diff --git a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts index ebc3344680d..0612f7c81f1 100644 --- a/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/service/reqresp/p2p_client.integration.test.ts @@ -139,6 +139,7 @@ describe('Req Resp p2p client integration', () => { epochProofQuotePool = { addQuote: jest.fn(), getQuotes: jest.fn().mockReturnValue([]), + deleteQuotesToEpoch: jest.fn(), }; blockSource = new MockBlockSource(); From 34d91cfb37310ae443bf8471e54072072794fe4c Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 11:54:52 +0000 Subject: [PATCH 39/43] Delete expired epoch quotes from pool --- l1-contracts/src/core/Leonidas.sol | 11 -- l1-contracts/src/core/Rollup.sol | 173 ++----------------- l1-contracts/src/core/interfaces/IRollup.sol | 8 +- yarn-project/p2p/src/client/p2p_client.ts | 2 + 4 files changed, 22 insertions(+), 172 deletions(-) diff --git a/l1-contracts/src/core/Leonidas.sol b/l1-contracts/src/core/Leonidas.sol index 726d42467c3..d243d6eed88 100644 --- a/l1-contracts/src/core/Leonidas.sol +++ b/l1-contracts/src/core/Leonidas.sol @@ -331,17 +331,6 @@ contract Leonidas is Ownable, ILeonidas { return Epoch.wrap(_slotNumber.unwrap() / EPOCH_DURATION); } - /** - * @notice Computes the epoch at a specific slot - * - * @param _slotNumber - The slot number to compute the epoch for - * - * @return The computed epoch - */ - function getEpochAtSlot(uint256 _slotNumber) public pure override(ILeonidas) returns (uint256) { - return _slotNumber / EPOCH_DURATION; - } - /** * @notice Adds a validator to the set WITHOUT setting up the epoch * @param _validator - The validator to add diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index d63e55f6be3..c9a05bf6a79 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -387,7 +387,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { uint256 pendingBlockNumber, bytes32 pendingArchive, bytes32 archiveOfMyBlock, - uint256 provenEpochNumber + Epoch provenEpochNumber ) { return ( @@ -409,15 +409,15 @@ contract Rollup is Leonidas, IRollup, ITestRollup { * @return uint256 - The slot at the given timestamp * @return uint256 - The block number at the given timestamp */ - function canProposeAtTime(uint256 _ts, bytes32 _archive) + function canProposeAtTime(Timestamp _ts, bytes32 _archive) external view override(IRollup) - returns (uint256, uint256) + returns (Slot, uint256) { - uint256 slot = getSlotAt(_ts); + Slot slot = getSlotAt(_ts); - uint256 lastSlot = uint256(blocks[tips.pendingBlockNumber].slotNumber); + Slot lastSlot = blocks[tips.pendingBlockNumber].slotNumber; if (slot <= lastSlot) { revert Errors.Rollup__SlotAlreadyInChain(lastSlot, slot); } @@ -452,7 +452,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { bytes calldata _header, SignatureLib.Signature[] memory _signatures, bytes32 _digest, - uint256 _currentTime, + Timestamp _currentTime, bytes32 _txsEffectsHash, DataStructures.ExecutionFlags memory _flags ) external view override(IRollup) { @@ -460,12 +460,12 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags); } - function nextEpochToClaim() external view override(IRollup) returns (uint256) { - uint256 epochClaimed = proofClaim.epochToProve; - if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) { - return 0; + function nextEpochToClaim() external view override(IRollup) returns (Epoch) { + Epoch epochClaimed = proofClaim.epochToProve; + if (proofClaim.proposerClaimant == address(0) && epochClaimed == Epoch.wrap(0)) { + return Epoch.wrap(0); } - return 1 + epochClaimed; + return Epoch.wrap(1) + epochClaimed; } function computeTxsEffectsHash(bytes calldata _body) @@ -483,8 +483,8 @@ contract Rollup is Leonidas, IRollup, ITestRollup { { validateEpochProofRightClaim(_quote); - uint256 currentSlot = getCurrentSlot(); - uint256 epochToProve = getEpochToProve(); + Slot currentSlot = getCurrentSlot(); + Epoch epochToProve = getEpochToProve(); // We don't currently unstake, // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. @@ -536,7 +536,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { _header: header, _signatures: _signatures, _digest: digest, - _currentTime: block.timestamp, + _currentTime: Timestamp.wrap(block.timestamp), _txEffectsHash: txsEffectsHash, _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) }); @@ -546,7 +546,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { blocks[blockNumber] = BlockLog({ archive: _archive, blockHash: _blockHash, - slotNumber: header.globalVariables.slotNumber.toUint128() + slotNumber: Slot.wrap(header.globalVariables.slotNumber) }); // @note The block number here will always be >=1 as the genesis block is at 0 @@ -578,33 +578,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } } - function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote) - public - override(IRollup) - { - validateEpochProofRightClaim(_quote); - - Slot currentSlot = getCurrentSlot(); - Epoch epochToProve = getEpochToProve(); - - // We don't currently unstake, - // but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652. - // Blocked on submitting epoch proofs to this contract. - PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover); - - proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epochToProve, - basisPointFee: _quote.quote.basisPointFee, - bondAmount: _quote.quote.bondAmount, - bondProvider: _quote.quote.prover, - proposerClaimant: msg.sender - }); - - emit ProofRightClaimed( - epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot - ); - } - /** * @notice Returns the computed public inputs for the given epoch proof. * @@ -734,120 +707,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return publicInputs; } - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) - public - view - override(IRollup) - { - uint256 currentSlot = getCurrentSlot(); - address currentProposer = getCurrentProposer(); - uint256 epochToProve = getEpochToProve(); - - if (currentProposer != address(0) && currentProposer != msg.sender) { - revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender); - } - - if (_quote.quote.epochToProve != epochToProve) { - revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve); - } - - if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) { - revert Errors.Rollup__NotInClaimPhase( - currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS - ); - } - - // if the epoch to prove is not the one that has been claimed, - // then whatever is in the proofClaim is stale - if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) { - revert Errors.Rollup__ProofRightAlreadyClaimed(); - } - - if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) { - revert Errors.Rollup__InsufficientBondAmount( - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount - ); - } - - if (_quote.quote.validUntilSlot < currentSlot) { - revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot); - } - } - - /** - * @notice Publishes the body and propose the block - * @dev `eth_log_handlers` rely on this function - * - * @param _header - The L2 block header - * @param _archive - A root of the archive tree after the L2 block is applied - * @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit - * @param _signatures - Signatures from the validators - * @param _body - The body of the L2 block - */ - function propose( - bytes calldata _header, - bytes32 _archive, - bytes32 _blockHash, - bytes32[] memory _txHashes, - SignatureLib.Signature[] memory _signatures, - bytes calldata _body - ) public override(IRollup) { - if (_canPrune()) { - _prune(); - } - bytes32 txsEffectsHash = TxsDecoder.decode(_body); - - // Decode and validate header - HeaderLib.Header memory header = HeaderLib.decode(_header); - - bytes32 digest = keccak256(abi.encode(_archive, _txHashes)); - setupEpoch(); - _validateHeader({ - _header: header, - _signatures: _signatures, - _digest: digest, - _currentTime: Timestamp.wrap(block.timestamp), - _txEffectsHash: txsEffectsHash, - _flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false}) - }); - - uint256 blockNumber = ++tips.pendingBlockNumber; - - blocks[blockNumber] = BlockLog({ - archive: _archive, - blockHash: _blockHash, - slotNumber: Slot.wrap(header.globalVariables.slotNumber) - }); - - // @note The block number here will always be >=1 as the genesis block is at 0 - bytes32 inHash = INBOX.consume(blockNumber); - if (header.contentCommitment.inHash != inHash) { - revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash); - } - - // TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim - // Min size = smallest path of the rollup tree + 1 - (uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs); - uint256 l2ToL1TreeMinHeight = min + 1; - OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight); - - emit L2BlockProposed(blockNumber, _archive); - - // Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber. - if (blockNumber <= assumeProvenThroughBlockNumber) { - tips.provenBlockNumber = blockNumber; - - if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) { - // @note This will currently fail if there are insufficient funds in the bridge - // which WILL happen for the old version after an upgrade where the bridge follow. - // Consider allowing a failure. See #7938. - FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees); - } - - emit L2ProofVerified(blockNumber, "CHEAT"); - } - } - function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) public view @@ -905,7 +764,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { return tips.pendingBlockNumber; } - function getEpochForBlock(uint256 blockNumber) public view override(IRollup) returns (uint256) { + function getEpochForBlock(uint256 blockNumber) public view override(IRollup) returns (Epoch) { if (blockNumber > tips.pendingBlockNumber) { revert Errors.Rollup__InvalidBlockNumber( tips.pendingBlockNumber, blockNumber diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index 0c22e85ac67..9e667fa924a 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -96,7 +96,7 @@ interface IRollup { uint256 pendingBlockNumber, bytes32 pendingArchive, bytes32 archiveOfMyBlock, - uint256 provenEpochNumber + Epoch provenEpochNumber ); // TODO(#7346): Integrate batch rollups @@ -115,9 +115,9 @@ interface IRollup { function archiveAt(uint256 _blockNumber) external view returns (bytes32); function getProvenBlockNumber() external view returns (uint256); function getPendingBlockNumber() external view returns (uint256); - function getEpochToProve() external view returns (uint256); - function nextEpochToClaim() external view returns (uint256); - function getEpochForBlock(uint256 blockNumber) external view returns (uint256); + function getEpochToProve() external view returns (Epoch); + function nextEpochToClaim() external view returns (Epoch); + function getEpochForBlock(uint256 blockNumber) external view returns (Epoch); function validateEpochProofRightClaim(DataStructures.SignedEpochProofQuote calldata _quote) external view; diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 6c6c1f6bdb5..36e32a5a463 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -498,6 +498,8 @@ export class P2PClient implements P2P { await this.markTxsAsMinedFromBlocks(blocks); const lastBlockNum = blocks[blocks.length - 1].number; await this.synchedLatestBlockNumber.set(lastBlockNum); + const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); + this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); this.log.debug(`Synched to latest block ${lastBlockNum}`); await this.startServiceIfSynched(); } From 7f96f0282375b1b4f52316814aa15fe03c1ba38c Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 12:09:35 +0000 Subject: [PATCH 40/43] Delete expired epoch quotes --- yarn-project/p2p/src/client/p2p_client.test.ts | 8 ++++++++ yarn-project/p2p/src/client/p2p_client.ts | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 4ce03c3b698..8e015b5b74c 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -79,6 +79,12 @@ describe('In-Memory P2P Client', () => { ); }; + afterEach(async () => { + if (client.isReady()) { + await client.stop(); + } + }); + it('can start & stop', async () => { expect(client.isReady()).toEqual(false); @@ -211,6 +217,8 @@ describe('In-Memory P2P Client', () => { client.broadcastEpochProofQuote(quote); } + epochProofQuotePool.deleteQuotesToEpoch.mockReset(); + await advanceToProvenBlock(3, 3); expect(epochProofQuotePool.deleteQuotesToEpoch).toBeCalledWith(3n); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 36e32a5a463..4c8322f19ca 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -498,8 +498,6 @@ export class P2PClient implements P2P { await this.markTxsAsMinedFromBlocks(blocks); const lastBlockNum = blocks[blocks.length - 1].number; await this.synchedLatestBlockNumber.set(lastBlockNum); - const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); - this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); this.log.debug(`Synched to latest block ${lastBlockNum}`); await this.startServiceIfSynched(); } @@ -529,6 +527,8 @@ export class P2PClient implements P2P { await this.synchedProvenBlockNumber.set(lastBlockNum); this.log.debug(`Synched to proven block ${lastBlockNum}`); + const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); + this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); await this.startServiceIfSynched(); } From e3d0cc7d20d6c1ae5d8bd6d3b9a0c1a580c8100f Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 12:13:34 +0000 Subject: [PATCH 41/43] Formatting --- l1-contracts/src/core/Rollup.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index c9a05bf6a79..5ba4f552143 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -766,9 +766,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { function getEpochForBlock(uint256 blockNumber) public view override(IRollup) returns (Epoch) { if (blockNumber > tips.pendingBlockNumber) { - revert Errors.Rollup__InvalidBlockNumber( - tips.pendingBlockNumber, blockNumber - ); + revert Errors.Rollup__InvalidBlockNumber(tips.pendingBlockNumber, blockNumber); } return getEpochAt(getTimestampForSlot(blocks[blockNumber].slotNumber)); } @@ -828,8 +826,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup { } Slot currentSlot = getCurrentSlot(); - Epoch oldestPendingEpoch = - getEpochForBlock(tips.provenBlockNumber + 1); + Epoch oldestPendingEpoch = getEpochForBlock(tips.provenBlockNumber + 1); Slot startSlotOfPendingEpoch = oldestPendingEpoch.toSlots(); // suppose epoch 1 is proven, epoch 2 is pending, epoch 3 is the current epoch. From f7fbbb4b3361aae5b02cc2728895e9f87d740825 Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 14:09:02 +0000 Subject: [PATCH 42/43] Handle no proven epochs --- yarn-project/archiver/src/archiver/archiver.ts | 5 +++-- yarn-project/archiver/src/archiver/archiver_store.ts | 2 +- .../archiver/src/archiver/kv_archiver_store/block_store.ts | 4 ++-- .../src/archiver/kv_archiver_store/kv_archiver_store.ts | 2 +- .../archiver/memory_archiver_store/memory_archiver_store.ts | 2 +- yarn-project/circuit-types/src/l2_block_source.ts | 2 +- yarn-project/p2p/src/client/mocks.ts | 4 ++-- yarn-project/p2p/src/client/p2p_client.ts | 4 +++- 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index 1b43d897eea..23f5f668254 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -295,6 +295,7 @@ export class Archiver implements ArchiveSource { ) { this.log.info(`Updating the proven block number to ${provenBlockNumber} and epoch to ${provenEpochNumber}`); await this.store.setProvenL2BlockNumber(Number(provenBlockNumber)); + // if we are here then we must have a valid proven epoch number await this.store.setProvenL2EpochNumber(Number(provenEpochNumber)); } }; @@ -516,7 +517,7 @@ export class Archiver implements ArchiveSource { return this.store.getProvenL2BlockNumber(); } - public getProvenL2EpochNumber(): Promise { + public getProvenL2EpochNumber(): Promise { return this.store.getProvenL2EpochNumber(); } @@ -767,7 +768,7 @@ class ArchiverStoreHelper getProvenL2BlockNumber(): Promise { return this.store.getProvenL2BlockNumber(); } - getProvenL2EpochNumber(): Promise { + getProvenL2EpochNumber(): Promise { return this.store.getProvenL2EpochNumber(); } setProvenL2BlockNumber(l2BlockNumber: number): Promise { diff --git a/yarn-project/archiver/src/archiver/archiver_store.ts b/yarn-project/archiver/src/archiver/archiver_store.ts index a04da4f524f..b181c44db39 100644 --- a/yarn-project/archiver/src/archiver/archiver_store.ts +++ b/yarn-project/archiver/src/archiver/archiver_store.ts @@ -150,7 +150,7 @@ export interface ArchiverDataStore { * Gets the number of the latest proven L2 epoch. * @returns The number of the latest proven L2 epoch. */ - getProvenL2EpochNumber(): Promise; + getProvenL2EpochNumber(): Promise; /** * Stores the number of the latest proven L2 block processed. diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts index f1383a09e74..42701f53d14 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/block_store.ts @@ -239,8 +239,8 @@ export class BlockStore { void this.#lastProvenL2Block.set(blockNumber); } - getProvenL2EpochNumber(): number { - return this.#lastProvenL2Epoch.get() ?? 0; + getProvenL2EpochNumber(): number | undefined { + return this.#lastProvenL2Epoch.get(); } setProvenL2EpochNumber(epochNumber: number) { diff --git a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts index 25a3394deae..baebf6efc64 100644 --- a/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/kv_archiver_store/kv_archiver_store.ts @@ -247,7 +247,7 @@ export class KVArchiverDataStore implements ArchiverDataStore { return Promise.resolve(this.#blockStore.getProvenL2BlockNumber()); } - getProvenL2EpochNumber(): Promise { + getProvenL2EpochNumber(): Promise { return Promise.resolve(this.#blockStore.getProvenL2EpochNumber()); } diff --git a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts index fd0b1f48cb4..df06ee022de 100644 --- a/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts +++ b/yarn-project/archiver/src/archiver/memory_archiver_store/memory_archiver_store.ts @@ -472,7 +472,7 @@ export class MemoryArchiverStore implements ArchiverDataStore { return Promise.resolve(this.lastProvenL2BlockNumber); } - public getProvenL2EpochNumber(): Promise { + public getProvenL2EpochNumber(): Promise { return Promise.resolve(this.lastProvenL2EpochNumber); } diff --git a/yarn-project/circuit-types/src/l2_block_source.ts b/yarn-project/circuit-types/src/l2_block_source.ts index d8ca2dde2eb..65bcf58d7de 100644 --- a/yarn-project/circuit-types/src/l2_block_source.ts +++ b/yarn-project/circuit-types/src/l2_block_source.ts @@ -37,7 +37,7 @@ export interface L2BlockSource { * Gets the number of the latest L2 proven epoch seen by the block source implementation. * @returns The number of the latest L2 proven epoch seen by the block source implementation. */ - getProvenL2EpochNumber(): Promise; + getProvenL2EpochNumber(): Promise; /** * Gets an l2 block. If a negative number is passed, the block returned is the most recent. diff --git a/yarn-project/p2p/src/client/mocks.ts b/yarn-project/p2p/src/client/mocks.ts index 2d216676a20..1783c83a8bd 100644 --- a/yarn-project/p2p/src/client/mocks.ts +++ b/yarn-project/p2p/src/client/mocks.ts @@ -7,7 +7,7 @@ import { EthAddress } from '@aztec/circuits.js'; export class MockBlockSource implements L2BlockSource { private l2Blocks: L2Block[] = []; private txEffects: TxEffect[] = []; - private provenEpochNumber = 0; + private provenEpochNumber: number = 0; constructor(numBlocks = 100, private provenBlockNumber?: number) { this.addBlocks(numBlocks); @@ -58,7 +58,7 @@ export class MockBlockSource implements L2BlockSource { return this.provenBlockNumber ?? (await this.getBlockNumber()); } - public getProvenL2EpochNumber(): Promise { + public getProvenL2EpochNumber(): Promise { return Promise.resolve(this.provenEpochNumber); } diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index 4c8322f19ca..0eded2b804d 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -528,7 +528,9 @@ export class P2PClient implements P2P { await this.synchedProvenBlockNumber.set(lastBlockNum); this.log.debug(`Synched to proven block ${lastBlockNum}`); const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); - this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); + if (provenEpochNumber !== undefined) { + this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); + } await this.startServiceIfSynched(); } From fcbe7ef1ddc61d2798c2bd6c9efe01bdbbda4eea Mon Sep 17 00:00:00 2001 From: PhilWindle Date: Fri, 27 Sep 2024 14:17:08 +0000 Subject: [PATCH 43/43] Fixed test --- yarn-project/sequencer-client/src/sequencer/sequencer.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index e474abd2cc6..bf3e21f0e6a 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -684,11 +684,11 @@ describe('sequencer', () => { publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x)); // The previous epoch can be claimed - publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); + publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(0n)); await sequencer.initialSync(); await sequencer.work(); - expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote); + expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined); }); it('does not submit a quote with an expired slot number', async () => {