diff --git a/l1-contracts/src/core/ProofCommitmentEscrow.sol b/l1-contracts/src/core/ProofCommitmentEscrow.sol deleted file mode 100644 index c11c2a35853f..000000000000 --- a/l1-contracts/src/core/ProofCommitmentEscrow.sol +++ /dev/null @@ -1,163 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; -import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; -import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; - -contract ProofCommitmentEscrow is IProofCommitmentEscrow { - using SafeERC20 for IERC20; - - struct WithdrawRequest { - uint256 amount; - Timestamp executableAt; - } - - uint256 public immutable WITHDRAW_DELAY; - - address public immutable ROLLUP; - IERC20 public immutable TOKEN; - - mapping(address => uint256) public deposits; - mapping(address => WithdrawRequest) public withdrawRequests; - - modifier onlyRollup() { - require(msg.sender == ROLLUP, Errors.ProofCommitmentEscrow__NotOwner(msg.sender)); - _; - } - - constructor( - IERC20 _token, - address _rollup, - uint256 _aztecSlotDuration, - uint256 _aztecEpochDuration - ) { - ROLLUP = _rollup; - TOKEN = _token; - WITHDRAW_DELAY = _aztecSlotDuration * _aztecEpochDuration; - } - - /** - * @notice Deposit TOKENs into the escrow - * - * @dev The caller must have approved the TOKEN transfer - * - * @param _amount The amount of TOKENs to deposit - */ - function deposit(uint256 _amount) external override(IProofCommitmentEscrow) { - TOKEN.safeTransferFrom(msg.sender, address(this), _amount); - - deposits[msg.sender] += _amount; - - emit Deposit(msg.sender, _amount); - } - - /** - * @notice Start a withdrawal request - * - * @dev The caller must have sufficient balance - * The withdrawal request will be executable after a delay - * Subsequent calls to this function will overwrite the previous request - * - * @param _amount - The amount of TOKENs to withdraw - */ - function startWithdraw(uint256 _amount) external override(IProofCommitmentEscrow) { - require( - deposits[msg.sender] >= _amount, - Errors.ProofCommitmentEscrow__InsufficientBalance(deposits[msg.sender], _amount) - ); - - withdrawRequests[msg.sender] = WithdrawRequest({ - amount: _amount, - executableAt: Timestamp.wrap(block.timestamp + WITHDRAW_DELAY) - }); - - emit StartWithdraw(msg.sender, _amount, withdrawRequests[msg.sender].executableAt); - } - - /** - * @notice Execute a mature withdrawal request - */ - function executeWithdraw() external override(IProofCommitmentEscrow) { - WithdrawRequest memory request = withdrawRequests[msg.sender]; - require( - request.executableAt <= Timestamp.wrap(block.timestamp), - Errors.ProofCommitmentEscrow__WithdrawRequestNotReady(block.timestamp, request.executableAt) - ); - - delete withdrawRequests[msg.sender]; - deposits[msg.sender] -= request.amount; - TOKEN.safeTransfer(msg.sender, request.amount); - - emit ExecuteWithdraw(msg.sender, request.amount); - } - - /** - * @notice Stake an amount of previously deposited TOKENs - * - * @dev Only callable by the owner - * The prover must have sufficient balance - * The prover's balance will be reduced by the bond amount - */ - function stakeBond(address _prover, uint256 _amount) - external - override(IProofCommitmentEscrow) - onlyRollup - { - deposits[_prover] -= _amount; - - emit StakeBond(_prover, _amount); - } - - /** - * @notice Unstake the bonded TOKENs, returning them to the prover - * - * @dev Only callable by the owner - */ - function unstakeBond(address _prover, uint256 _amount) - external - override(IProofCommitmentEscrow) - onlyRollup - { - deposits[_prover] += _amount; - - emit UnstakeBond(_prover, _amount); - } - - function token() external view override(IProofCommitmentEscrow) returns (IERC20) { - return TOKEN; - } - - /** - * @notice Get the minimum balance of a prover at a given timestamp. - * - * @dev Returns 0 if the timestamp is beyond the WITHDRAW_DELAY from the current block timestamp - * - * @param _timestamp The timestamp at which to check the balance - * @param _prover The address of the prover - * - * @return The balance of the prover at the given timestamp, compensating for withdrawal requests that have matured by that time - */ - function minBalanceAtTime(Timestamp _timestamp, address _prover) - external - view - override(IProofCommitmentEscrow) - returns (uint256) - { - // If the timestamp is beyond the WITHDRAW_DELAY, the minimum possible balance is 0; - // the prover could issue a withdraw request in this block for the full amount, - // and execute it exactly WITHDRAW_DELAY later. - if (_timestamp >= Timestamp.wrap(block.timestamp + WITHDRAW_DELAY)) { - return 0; - } - - uint256 balance = deposits[_prover]; - if (withdrawRequests[_prover].executableAt <= _timestamp) { - balance -= withdrawRequests[_prover].amount; - } - return balance; - } -} diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index cd5345c95d4f..6baeb9628e6a 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -35,7 +35,8 @@ import { Signature, DataStructures, ExtRollupLib, - IntRollupLib + IntRollupLib, + EpochRewards } from "./RollupCore.sol"; // solhint-enable no-unused-import @@ -137,15 +138,6 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { ); } - function getProofClaim() - external - view - override(IRollup) - returns (DataStructures.EpochProofClaim memory) - { - return rollupStore.proofClaim; - } - /** * @notice Returns the computed public inputs for the given epoch proof. * @@ -153,40 +145,23 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { * own public inputs used for generating the proof vs the ones assembled * by this contract when verifying it. * - * @param _epochSize - The size of the epoch (to be promoted to a constant) + * @param _start - The start of the epoch (inclusive) + * @param _end - The end of the epoch (inclusive) * @param _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) * @param _fees - Array of recipient-value pairs with fees to be distributed for the epoch * @param _aggregationObject - The aggregation object for the proof */ function getEpochProofPublicInputs( - uint256 _epochSize, + uint256 _start, + uint256 _end, bytes32[7] calldata _args, bytes32[] calldata _fees, bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) external view override(IRollup) returns (bytes32[] memory) { return ExtRollupLib.getEpochProofPublicInputs( - rollupStore, _epochSize, _args, _fees, _blobPublicInputs, _aggregationObject - ); - } - - /** - * @notice Get the next epoch that can be claimed - * @dev Will revert if the epoch has already been claimed or if there is no epoch to prove - */ - function getClaimableEpoch() external view override(IRollup) returns (Epoch) { - Epoch epochToProve = getEpochToProve(); - require( - // If the epoch has been claimed, it cannot be claimed again - rollupStore.proofClaim.epochToProve != epochToProve - // Edge case for if no claim has been made yet. - // We know that the bondProvider is always set, - // Since otherwise the claimEpochProofRight would have reverted, - // because the zero address cannot have deposited funds into escrow. - || rollupStore.proofClaim.bondProvider == address(0), - Errors.Rollup__ProofRightAlreadyClaimed() + rollupStore, _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); - return epochToProve; } /** @@ -455,6 +430,57 @@ contract Rollup is IStaking, IValidatorSelection, IRollup, RollupCore { return _slotNumber.epochFromSlot(); } + function getProofSubmissionWindow() external view override(IRollup) returns (uint256) { + return PROOF_SUBMISSION_WINDOW; + } + + function getSequencerRewards(address _sequencer) + external + view + override(IRollup) + returns (uint256) + { + return sequencerRewards[_sequencer]; + } + + function getCollectiveProverRewardsForEpoch(Epoch _epoch) + external + view + override(IRollup) + returns (uint256) + { + return epochRewards[_epoch].rewards; + } + + function getSpecificProverRewardsForEpoch(Epoch _epoch, address _prover) + external + view + override(IRollup) + returns (uint256) + { + EpochRewards storage er = epochRewards[_epoch]; + uint256 length = er.longestProvenLength; + + if (er.subEpoch[length].hasSubmitted[_prover]) { + return er.rewards / er.subEpoch[length].summedCount; + } + + return 0; + } + + function getHasSubmitted(Epoch _epoch, uint256 _length, address _prover) + external + view + override(IRollup) + returns (bool) + { + return epochRewards[_epoch].subEpoch[_length].hasSubmitted[_prover]; + } + + function getProvingCostPerMana() external view override(IRollup) returns (uint256) { + return provingCostPerMana; + } + /** * @notice Check if msg.sender can propose at a given time * diff --git a/l1-contracts/src/core/RollupCore.sol b/l1-contracts/src/core/RollupCore.sol index 34a5d6d0100c..bc2110b7fdac 100644 --- a/l1-contracts/src/core/RollupCore.sol +++ b/l1-contracts/src/core/RollupCore.sol @@ -3,7 +3,6 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import { IRollupCore, ITestRollup, @@ -14,7 +13,9 @@ import { RollupStore, L1GasOracleValues, L1FeeData, - SubmitEpochRootProofArgs + SubmitEpochRootProofArgs, + SubEpochRewards, + EpochRewards } from "@aztec/core/interfaces/IRollup.sol"; import {IStakingCore} from "@aztec/core/interfaces/IStaking.sol"; import {IValidatorSelectionCore} from "@aztec/core/interfaces/IValidatorSelection.sol"; @@ -29,11 +30,9 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; import { ExtRollupLib, ValidateHeaderArgs, - Header, - SignedEpochProofQuote, - SubmitEpochRootProofInterimValues + Header } from "@aztec/core/libraries/RollupLibs/ExtRollupLib.sol"; -import {IntRollupLib, EpochProofQuote} from "@aztec/core/libraries/RollupLibs/IntRollupLib.sol"; +import {IntRollupLib} from "@aztec/core/libraries/RollupLibs/IntRollupLib.sol"; import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {StakingLib} from "@aztec/core/libraries/staking/StakingLib.sol"; import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol"; @@ -41,24 +40,44 @@ import {ValidatorSelectionLib} from "@aztec/core/libraries/ValidatorSelectionLib/ValidatorSelectionLib.sol"; import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; -import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; import {Slasher} from "@aztec/core/staking/Slasher.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {MockVerifier} from "@aztec/mock/MockVerifier.sol"; import {Ownable} from "@oz/access/Ownable.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {EIP712} from "@oz/utils/cryptography/EIP712.sol"; +import {Math} from "@oz/utils/math/Math.sol"; struct Config { uint256 aztecSlotDuration; uint256 aztecEpochDuration; uint256 targetCommitteeSize; - uint256 aztecEpochProofClaimWindowInL2Slots; + uint256 aztecProofSubmissionWindow; uint256 minimumStake; uint256 slashingQuorum; uint256 slashingRoundSize; } +// @note https://www.youtube.com/watch?v=glN0W8WogK8 +struct SubmitProofInterim { + Slot deadline; + uint256 length; + uint256 totalBurn; + address prover; + uint256 feesToClaim; + uint256 fee; + uint256 proverFee; + uint256 burn; + uint256 blockRewardsAvailable; + uint256 blockRewardSequencer; + uint256 blockRewardProver; + uint256 added; + uint256 sequencerShare; + bool isFeeCanonical; + bool isRewardDistributorCanonical; + uint256 feeAssetPrice; +} + /** * @title Rollup * @author Aztec Labs @@ -85,10 +104,6 @@ contract RollupCore is Slot public constant LIFETIME = Slot.wrap(5); Slot public constant LAG = Slot.wrap(2); - // 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 PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST = 1000; - // A Cuauhxicalli [kʷaːʍʃiˈkalːi] ("eagle gourd bowl") is a ceremonial Aztec vessel or altar used to hold offerings, // such as sacrificial hearts, during rituals performed within temples. address public constant CUAUHXICALLI = address(bytes20("CUAUHXICALLI")); @@ -96,11 +111,12 @@ contract RollupCore is address public constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); bool public immutable IS_FOUNDRY_TEST; - uint256 public immutable CLAIM_DURATION_IN_L2_SLOTS; + // The number of slots, measured from the beginning on an epoch, that a proof will be accepted within. + uint256 internal immutable PROOF_SUBMISSION_WINDOW; + uint256 public immutable L1_BLOCK_AT_GENESIS; IInbox public immutable INBOX; IOutbox public immutable OUTBOX; - IProofCommitmentEscrow public immutable PROOF_COMMITMENT_ESCROW; uint256 public immutable VERSION; IFeeJuicePortal public immutable FEE_JUICE_PORTAL; IRewardDistributor public immutable REWARD_DISTRIBUTOR; @@ -118,6 +134,10 @@ contract RollupCore is // Testing only. This should be removed eventually. uint256 private assumeProvenThroughBlockNumber; + mapping(address => uint256) internal sequencerRewards; + mapping(Epoch => EpochRewards) internal epochRewards; + uint256 internal provingCostPerMana = 100; + constructor( IFeeJuicePortal _fpcJuicePortal, IRewardDistributor _rewardDistributor, @@ -129,6 +149,8 @@ contract RollupCore is ) Ownable(_ares) { TimeLib.initialize(block.timestamp, _config.aztecSlotDuration, _config.aztecEpochDuration); + PROOF_SUBMISSION_WINDOW = _config.aztecProofSubmissionWindow; + Timestamp exitDelay = Timestamp.wrap(60 * 60 * 24); Slasher slasher = new Slasher(_config.slashingQuorum, _config.slashingRoundSize); StakingLib.initialize(_stakingAsset, _config.minimumStake, exitDelay, address(slasher)); @@ -137,14 +159,10 @@ contract RollupCore is FEE_JUICE_PORTAL = _fpcJuicePortal; REWARD_DISTRIBUTOR = _rewardDistributor; ASSET = _fpcJuicePortal.UNDERLYING(); - PROOF_COMMITMENT_ESCROW = new ProofCommitmentEscrow( - ASSET, address(this), _config.aztecSlotDuration, _config.aztecEpochDuration - ); INBOX = IInbox(address(new Inbox(address(this), Constants.L1_TO_L2_MSG_SUBTREE_HEIGHT))); OUTBOX = IOutbox(address(new Outbox(address(this)))); VERSION = 1; L1_BLOCK_AT_GENESIS = block.number; - CLAIM_DURATION_IN_L2_SLOTS = _config.aztecEpochProofClaimWindowInL2Slots; IS_FOUNDRY_TEST = VM_ADDRESS.code.length > 0; @@ -270,27 +288,6 @@ contract RollupCore is rollupStore.protocolContractTreeRoot = _protocolContractTreeRoot; } - /** - * @notice Publishes the body and propose the block - * @dev `eth_log_handlers` rely on this function - * - * @param _args - The arguments to propose the block - * @param _signatures - Signatures from the validators - * // TODO(#9101): The below _body should be removed once we can extract blobs. It's only here so the archiver can extract tx effects. - * @param _body - The body of the L2 block - * @param _blobInput - The blob evaluation KZG proof, challenge, and opening required for the precompile. - */ - function proposeAndClaim( - ProposeArgs calldata _args, - Signature[] memory _signatures, - bytes calldata _body, - bytes calldata _blobInput, - SignedEpochProofQuote calldata _quote - ) external override(IRollupCore) { - propose(_args, _signatures, _body, _blobInput); - claimEpochProofRight(_quote); - } - /** * @notice Submit a proof for an epoch in the pending chain * @@ -321,58 +318,118 @@ contract RollupCore is _prune(); } - // We want to compute the two epoch values before hand. Could we do partial interim? - // We compute these in here to avoid a lot of pain with linking libraries and passing - // external functions into internal functions as args. - SubmitEpochRootProofInterimValues memory interimValues; - interimValues.previousBlockNumber = rollupStore.tips.provenBlockNumber; - interimValues.endBlockNumber = interimValues.previousBlockNumber + _args.epochSize; - - // @note The _getEpochForBlock is expected to revert if the block is beyond pending. - // If this changes you are gonna get so rekt you won't believe it. - // I mean proving blocks that have been pruned rekt. - interimValues.startEpoch = getEpochForBlock(interimValues.previousBlockNumber + 1); - interimValues.epochToProve = getEpochForBlock(interimValues.endBlockNumber); - - uint256 endBlockNumber = ExtRollupLib.submitEpochRootProof( - rollupStore, - _args, - interimValues, - PROOF_COMMITMENT_ESCROW, - FEE_JUICE_PORTAL, - REWARD_DISTRIBUTOR, - ASSET, - CUAUHXICALLI + SubmitProofInterim memory interim; + + // Start of `isAcceptable` + Epoch startEpoch = getEpochForBlock(_args.start); + // This also checks for existence of the block. + Epoch endEpoch = getEpochForBlock(_args.end); + + require(startEpoch == endEpoch, Errors.Rollup__StartAndEndNotSameEpoch(startEpoch, endEpoch)); + + interim.deadline = startEpoch.toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); + require( + interim.deadline >= Timestamp.wrap(block.timestamp).slotFromTimestamp(), "past deadline" ); - emit L2ProofVerified(endBlockNumber, _args.args[6]); - } - function setupEpoch() public override(IValidatorSelectionCore) { - ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); - } + // By making sure that the previous block is in another epoch, we know that we were + // at the start. + Epoch parentEpoch = getEpochForBlock(_args.start - 1); - function claimEpochProofRight(SignedEpochProofQuote calldata _quote) public override(IRollupCore) { - validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote); + require(startEpoch > Epoch.wrap(0) || _args.start == 1, "invalid first epoch proof"); - Slot currentSlot = Timestamp.wrap(block.timestamp).slotFromTimestamp(); - Epoch epochToProve = getEpochToProve(); + bool isStartOfEpoch = _args.start == 1 || parentEpoch <= startEpoch - Epoch.wrap(1); + require(isStartOfEpoch, Errors.Rollup__StartIsNotFirstBlockOfEpoch()); - // 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.prover, _quote.quote.bondAmount); + bool isStartBuildingOnProven = _args.start - 1 <= rollupStore.tips.provenBlockNumber; + require(isStartBuildingOnProven, Errors.Rollup__StartIsNotBuildingOnProven()); - rollupStore.proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epochToProve, - basisPointFee: _quote.quote.basisPointFee, - bondAmount: _quote.quote.bondAmount, - bondProvider: _quote.quote.prover, - proposerClaimant: msg.sender - }); + // End of `isAcceptable` - emit ProofRightClaimed( - epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot - ); + // Start of verifying the proof + require(ExtRollupLib.verifyEpochRootProof(rollupStore, _args), "proof is invalid"); + // End of verifying the proof + + interim.isFeeCanonical = address(this) == FEE_JUICE_PORTAL.canonicalRollup(); + interim.isRewardDistributorCanonical = address(this) == REWARD_DISTRIBUTOR.canonicalRollup(); + + // Mark that the prover has submitted a proof + // Only do this if we are canonical for both fees and block rewards. + if (interim.isFeeCanonical && interim.isRewardDistributorCanonical) { + interim.prover = address(bytes20(_args.args[6] << 96)); // The address is left padded within the bytes32 + + interim.length = _args.end - _args.start + 1; + EpochRewards storage er = epochRewards[endEpoch]; + SubEpochRewards storage sr = er.subEpoch[interim.length]; + sr.summedCount += 1; + + // Using the prover id to ensure proof only gets added once + require(!sr.hasSubmitted[interim.prover], "go away"); + sr.hasSubmitted[interim.prover] = true; + + if (interim.length > er.longestProvenLength) { + interim.added = interim.length - er.longestProvenLength; + interim.blockRewardsAvailable = interim.isRewardDistributorCanonical + ? REWARD_DISTRIBUTOR.claimBlockRewards(address(this), interim.added) + : 0; + interim.sequencerShare = interim.blockRewardsAvailable / 2; + interim.blockRewardSequencer = interim.sequencerShare / interim.added; + interim.blockRewardProver = interim.blockRewardsAvailable - interim.sequencerShare; + + for (uint256 i = er.longestProvenLength; i < interim.length; i++) { + FeeHeader storage feeHeader = rollupStore.blocks[_args.start + i].feeHeader; + + (interim.fee, interim.burn) = interim.isFeeCanonical + ? (uint256(_args.fees[1 + i * 2]), feeHeader.congestionCost * feeHeader.manaUsed) + : (0, 0); + + interim.feesToClaim += interim.fee; + interim.fee -= interim.burn; + interim.totalBurn += interim.burn; + + // Compute the proving fee in the fee asset + { + // @todo likely better for us to store this if we can pack it better + interim.feeAssetPrice = IntRollupLib.feeAssetPriceModifier( + rollupStore.blocks[_args.start + i - 1].feeHeader.feeAssetPriceNumerator + ); + } + interim.proverFee = Math.min( + feeHeader.manaUsed + * Math.mulDiv(provingCostPerMana, interim.feeAssetPrice, 1e9, Math.Rounding.Ceil), + interim.fee + ); + + interim.fee -= interim.proverFee; + + er.rewards += interim.proverFee; + // The address is left padded within the bytes32 + sequencerRewards[address(bytes20(_args.fees[i * 2] << 96))] += + (interim.blockRewardSequencer + interim.fee); + } + + er.rewards += interim.blockRewardProver; + + er.longestProvenLength = interim.length; + + FEE_JUICE_PORTAL.distributeFees(address(this), interim.feesToClaim); + } + + // @todo Get the block rewards for + + if (interim.totalBurn > 0 && interim.isFeeCanonical) { + ASSET.transfer(CUAUHXICALLI, interim.totalBurn); + } + } + + // Update the proven block number + rollupStore.tips.provenBlockNumber = Math.max(rollupStore.tips.provenBlockNumber, _args.end); + + emit L2ProofVerified(_args.end, _args.args[6]); + } + + function setupEpoch() public override(IValidatorSelectionCore) { + ValidatorSelectionLib.setupEpoch(StakingLib.getStorage()); } /** @@ -531,47 +588,12 @@ contract RollupCore is return ExtRollupLib.getManaBaseFeeComponentsAt( rollupStore.blocks[blockOfInterest].feeHeader, getL1FeesAt(_timestamp), + provingCostPerMana, _inFeeAsset ? getFeeAssetPrice() : 1e9, TimeLib.getStorage().epochDuration ); } - function quoteToDigest(EpochProofQuote memory _quote) - public - view - override(IRollupCore) - returns (bytes32) - { - return _hashTypedDataV4(IntRollupLib.computeQuoteHash(_quote)); - } - - function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) - public - view - override(IRollupCore) - { - Slot currentSlot = _ts.slotFromTimestamp(); - address currentProposer = ValidatorSelectionLib.getProposerAt( - StakingLib.getStorage(), currentSlot, currentSlot.epochFromSlot() - ); - Epoch epochToProve = getEpochToProve(); - uint256 posInEpoch = TimeLib.positionInEpoch(currentSlot); - bytes32 digest = quoteToDigest(_quote.quote); - - ExtRollupLib.validateEpochProofRightClaimAtTime( - currentSlot, - currentProposer, - epochToProve, - posInEpoch, - _quote, - digest, - rollupStore.proofClaim, - CLAIM_DURATION_IN_L2_SLOTS, - PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, - PROOF_COMMITMENT_ESCROW - ); - } - function getEpochForBlock(uint256 _blockNumber) public view override(IRollupCore) returns (Epoch) { require( _blockNumber <= rollupStore.tips.pendingBlockNumber, @@ -609,33 +631,13 @@ contract RollupCore is return false; } - Slot currentSlot = _ts.slotFromTimestamp(); Epoch oldestPendingEpoch = getEpochForBlock(rollupStore.tips.provenBlockNumber + 1); - Slot startSlotOfPendingEpoch = oldestPendingEpoch.toSlots(); - - // 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 + TimeLib.toSlots(Epoch.wrap(1)) - + Slot.wrap(CLAIM_DURATION_IN_L2_SLOTS); - - bool claimExists = currentSlot < startSlotOfPendingEpoch + TimeLib.toSlots(Epoch.wrap(2)) - && rollupStore.proofClaim.epochToProve == oldestPendingEpoch - && rollupStore.proofClaim.proposerClaimant != address(0); - - if (inClaimPhase || claimExists) { - // If we are in the claim phase, do not prune - return false; - } - return true; + Slot deadline = oldestPendingEpoch.toSlots() + Slot.wrap(PROOF_SUBMISSION_WINDOW); + + return deadline < _ts.slotFromTimestamp(); } function _prune() internal { - // TODO #8656 - delete rollupStore.proofClaim; - uint256 pending = rollupStore.tips.pendingBlockNumber; // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. @@ -761,22 +763,6 @@ contract RollupCore is && _blockNumber <= rollupStore.tips.pendingBlockNumber ) { rollupStore.tips.provenBlockNumber = _blockNumber; - - // If this results on a new epoch, create a fake claim for it - // Otherwise nextEpochToProve will report an old epoch - Epoch epoch = getEpochForBlock(_blockNumber); - if ( - Epoch.unwrap(epoch) == 0 - || Epoch.unwrap(epoch) > Epoch.unwrap(rollupStore.proofClaim.epochToProve) - ) { - rollupStore.proofClaim = DataStructures.EpochProofClaim({ - epochToProve: epoch, - basisPointFee: 0, - bondAmount: 0, - bondProvider: address(0), - proposerClaimant: msg.sender - }); - } } } } diff --git a/l1-contracts/src/core/interfaces/IInstance.sol b/l1-contracts/src/core/interfaces/IInstance.sol new file mode 100644 index 000000000000..598dcbd362c6 --- /dev/null +++ b/l1-contracts/src/core/interfaces/IInstance.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {IRollup, ITestRollup} from "./IRollup.sol"; +import {IStaking} from "./IStaking.sol"; +import {IValidatorSelection} from "./IValidatorSelection.sol"; + +interface IInstance is IStaking, IValidatorSelection, IRollup, ITestRollup {} diff --git a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol b/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol deleted file mode 100644 index 87ef34e02196..000000000000 --- a/l1-contracts/src/core/interfaces/IProofCommitmentEscrow.sol +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; -import {IERC20} from "@oz/token/ERC20/IERC20.sol"; - -interface IProofCommitmentEscrow { - event Deposit(address indexed depositor, uint256 amount); - event StartWithdraw(address indexed withdrawer, uint256 amount, Timestamp executableAt); - event ExecuteWithdraw(address indexed withdrawer, uint256 amount); - event StakeBond(address indexed prover, uint256 amount); - event UnstakeBond(address indexed prover, uint256 amount); - - function deposit(uint256 _amount) external; - function startWithdraw(uint256 _amount) external; - function executeWithdraw() external; - function stakeBond(address _prover, uint256 _amount) external; - function unstakeBond(address _prover, uint256 _amount) external; - function minBalanceAtTime(Timestamp _timestamp, address _prover) external view returns (uint256); - function deposits(address) external view returns (uint256); - function token() external view returns (IERC20); -} diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index edda9efec3c3..ea9667221506 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -7,10 +7,6 @@ import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol"; import {IOutbox} from "@aztec/core/interfaces/messagebridge/IOutbox.sol"; import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; -import { - EpochProofQuote, - SignedEpochProofQuote -} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; import { FeeHeader, L1FeeData, ManaBaseFeeComponents } from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; @@ -18,8 +14,9 @@ import {ProposeArgs} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {Timestamp, Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; struct SubmitEpochRootProofArgs { - uint256 epochSize; - bytes32[7] args; + uint256 start; // inclusive + uint256 end; // inclusive + bytes32[7] args; // @todo These are obhorrent and so easy to mess up with wrong padding. bytes32[] fees; bytes blobPublicInputs; bytes aggregationObject; @@ -44,6 +41,17 @@ struct L1GasOracleValues { Slot slotOfChange; } +struct SubEpochRewards { + uint256 summedCount; + mapping(address prover => bool proofSubmitted) hasSubmitted; +} + +struct EpochRewards { + uint256 longestProvenLength; + uint256 rewards; + mapping(uint256 length => SubEpochRewards) subEpoch; +} + // The below blobPublicInputsHashes are filled when proposing a block, then used to verify an epoch proof. // TODO(#8955): When implementing batched kzg proofs, store one instance per epoch rather than block struct RollupStore { @@ -53,7 +61,6 @@ struct RollupStore { bytes32 vkTreeRoot; bytes32 protocolContractTreeRoot; L1GasOracleValues l1GasOracleValues; - DataStructures.EpochProofClaim proofClaim; IVerifier epochProofVerifier; } @@ -82,19 +89,10 @@ interface IRollupCore { ); event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId); event PrunedPending(uint256 provenBlockNumber, uint256 pendingBlockNumber); - event ProofRightClaimed( - Epoch indexed epoch, - address indexed bondProvider, - address indexed proposer, - uint256 bondAmount, - Slot currentSlot - ); function prune() external; function updateL1GasFeeOracle() external; - function claimEpochProofRight(SignedEpochProofQuote calldata _quote) external; - function propose( ProposeArgs calldata _args, Signature[] memory _signatures, @@ -102,14 +100,6 @@ interface IRollupCore { bytes calldata _blobInput ) external; - function proposeAndClaim( - ProposeArgs calldata _args, - Signature[] memory _signatures, - bytes calldata _body, - bytes calldata _blobInput, - SignedEpochProofQuote calldata _quote - ) external; - function submitEpochRootProof(SubmitEpochRootProofArgs calldata _args) external; // solhint-disable-next-line func-name-mixedcase @@ -121,8 +111,6 @@ interface IRollupCore { // solhint-disable-next-line func-name-mixedcase function L1_BLOCK_AT_GENESIS() external view returns (uint256); - function quoteToDigest(EpochProofQuote memory _quote) external view returns (bytes32); - function getFeeAssetPrice() external view returns (uint256); function getL1FeesAt(Timestamp _timestamp) external view returns (L1FeeData memory); @@ -130,9 +118,6 @@ interface IRollupCore { function canPruneAtTime(Timestamp _ts) external view returns (bool); function getEpochToProve() external view returns (Epoch); - function validateEpochProofRightClaimAtTime(Timestamp _ts, SignedEpochProofQuote calldata _quote) - external - view; function getEpochForBlock(uint256 _blockNumber) external view returns (Epoch); } @@ -151,18 +136,15 @@ interface IRollup is IRollupCore { Epoch provenEpochNumber ); - function getProofClaim() external view returns (DataStructures.EpochProofClaim memory); - function getEpochProofPublicInputs( - uint256 _epochSize, + uint256 _start, + uint256 _end, bytes32[7] calldata _args, bytes32[] calldata _fees, bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) external view returns (bytes32[] memory); - function getClaimableEpoch() external view returns (Epoch); - function validateHeader( bytes calldata _header, Signature[] memory _signatures, @@ -187,4 +169,19 @@ interface IRollup is IRollupCore { function getPendingBlockNumber() external view returns (uint256); function getBlock(uint256 _blockNumber) external view returns (BlockLog memory); function getBlobPublicInputsHash(uint256 _blockNumber) external view returns (bytes32); + + function getSequencerRewards(address _sequencer) external view returns (uint256); + function getCollectiveProverRewardsForEpoch(Epoch _epoch) external view returns (uint256); + function getSpecificProverRewardsForEpoch(Epoch _epoch, address _prover) + external + view + returns (uint256); + function getHasSubmitted(Epoch _epoch, uint256 _length, address _prover) + external + view + returns (bool); + + function getProofSubmissionWindow() external view returns (uint256); + + function getProvingCostPerMana() external view returns (uint256); } diff --git a/l1-contracts/src/core/libraries/DataStructures.sol b/l1-contracts/src/core/libraries/DataStructures.sol index 66b9acbbc526..88bac5713722 100644 --- a/l1-contracts/src/core/libraries/DataStructures.sol +++ b/l1-contracts/src/core/libraries/DataStructures.sol @@ -2,8 +2,6 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; - /** * @title Data Structures Library * @author Aztec Labs @@ -76,20 +74,4 @@ library DataStructures { bool ignoreDA; bool ignoreSignatures; } - - /** - * @notice Struct containing the Epoch Proof Claim - * @param epochToProve - the epoch that the bond provider is claiming to prove - * @param basisPointFee the fee that the bond provider will receive as a percentage of the block rewards - * @param bondAmount - the size of the bond - * @param bondProvider - the address that put up the bond - * @param proposerClaimant - the address of the proposer that submitted the claim - */ - struct EpochProofClaim { - Epoch epochToProve; - uint256 basisPointFee; - uint256 bondAmount; - address bondProvider; - address proposerClaimant; - } } diff --git a/l1-contracts/src/core/libraries/Errors.sol b/l1-contracts/src/core/libraries/Errors.sol index afd700cc04a1..ef804c772133 100644 --- a/l1-contracts/src/core/libraries/Errors.sol +++ b/l1-contracts/src/core/libraries/Errors.sol @@ -66,11 +66,7 @@ library Errors { error Rollup__InvalidBlobPublicInputsHash(bytes32 expected, bytes32 actual); // 0xfe6b4994 error Rollup__NoEpochToProve(); // 0xcbaa3951 error Rollup__NonSequentialProving(); // 0x1e5be132 - error Rollup__NotClaimingCorrectEpoch(Epoch expected, Epoch actual); // 0xf0e0744d error Rollup__NothingToPrune(); // 0x850defd3 - error Rollup__NotInClaimPhase(uint256 currentSlotInEpoch, uint256 claimDuration); // 0xe6969f11 - error Rollup__ProofRightAlreadyClaimed(); // 0x2cac5f0a - error Rollup__QuoteExpired(Slot currentSlot, Slot quoteSlot); // 0x20a001eb error Rollup__SlotAlreadyInChain(Slot lastSlot, Slot proposedSlot); // 0x83510bd0 error Rollup__TimestampInFuture(Timestamp max, Timestamp actual); // 0x89f30690 error Rollup__TimestampTooOld(); // 0x72ed9c81 @@ -80,6 +76,9 @@ library Errors { error Rollup__NonZeroL2Fee(); // 0x7e728abc error Rollup__InvalidBasisPointFee(uint256 basisPointFee); // 0x4292d136 error Rollup__InvalidManaBaseFee(uint256 expected, uint256 actual); // 0x73b6d896 + error Rollup__StartAndEndNotSameEpoch(Epoch start, Epoch end); + error Rollup__StartIsNotFirstBlockOfEpoch(); + error Rollup__StartIsNotBuildingOnProven(); // HeaderLib error HeaderLib__InvalidHeaderSize(uint256 expected, uint256 actual); // 0xf3ccb247 diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol index ef87ec9eb508..3c65d312507b 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/EpochProofLib.sol @@ -3,20 +3,15 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; -import { - RollupStore, SubmitEpochRootProofArgs, FeeHeader -} from "@aztec/core/interfaces/IRollup.sol"; +import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Epoch} from "@aztec/core/libraries/TimeLib.sol"; import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; import {SafeERC20} from "@oz/token/ERC20/utils/SafeERC20.sol"; -import {Math} from "@oz/utils/math/Math.sol"; struct SubmitEpochRootProofAddresses { - IProofCommitmentEscrow proofCommitmentEscrow; IFeeJuicePortal feeJuicePortal; IRewardDistributor rewardDistributor; IERC20 asset; @@ -37,25 +32,18 @@ struct SubmitEpochRootProofInterimValues { library EpochProofLib { using SafeERC20 for IERC20; - function submitEpochRootProof( + function verifyEpochRootProof( RollupStore storage _rollupStore, - SubmitEpochRootProofArgs calldata _args, - SubmitEpochRootProofInterimValues memory _interimValues, - SubmitEpochRootProofAddresses memory _addresses - ) internal returns (uint256) { - // Ensure that the proof is not across epochs - require( - _interimValues.startEpoch == _interimValues.epochToProve, - Errors.Rollup__InvalidEpoch(_interimValues.startEpoch, _interimValues.epochToProve) - ); + SubmitEpochRootProofArgs calldata _args + ) internal view returns (bool) { + uint256 size = _args.end - _args.start + 1; - for (uint256 i = 0; i < _args.epochSize; i++) { - // This was moved from getEpochProofPublicInputs() due to stack too deep + for (uint256 i = 0; i < size; i++) { uint256 blobOffset = i * Constants.BLOB_PUBLIC_INPUTS_BYTES + i; uint8 blobsInBlock = uint8(_args.blobPublicInputs[blobOffset++]); checkBlobPublicInputsHashes( _args.blobPublicInputs, - _rollupStore.blobPublicInputsHashes[_interimValues.previousBlockNumber + i + 1], + _rollupStore.blobPublicInputsHashes[_args.start + i], blobOffset, blobsInBlock ); @@ -63,7 +51,8 @@ library EpochProofLib { bytes32[] memory publicInputs = getEpochProofPublicInputs( _rollupStore, - _args.epochSize, + _args.start, + _args.end, _args.args, _args.fees, _args.blobPublicInputs, @@ -75,80 +64,7 @@ library EpochProofLib { Errors.Rollup__InvalidProof() ); - if (_rollupStore.proofClaim.epochToProve == _interimValues.epochToProve) { - _addresses.proofCommitmentEscrow.unstakeBond( - _rollupStore.proofClaim.bondProvider, _rollupStore.proofClaim.bondAmount - ); - } - - _rollupStore.tips.provenBlockNumber = _interimValues.endBlockNumber; - - // @note Only if the rollup is the canonical will it be able to meaningfully claim fees - // Otherwise, the fees are unbacked #7938. - _interimValues.isFeeCanonical = address(this) == _addresses.feeJuicePortal.canonicalRollup(); - _interimValues.isRewardDistributorCanonical = - address(this) == _addresses.rewardDistributor.canonicalRollup(); - - _interimValues.totalProverReward = 0; - _interimValues.totalBurn = 0; - - if (_interimValues.isFeeCanonical || _interimValues.isRewardDistributorCanonical) { - for (uint256 i = 0; i < _args.epochSize; i++) { - address coinbase = address(uint160(uint256(publicInputs[9 + i * 2]))); - uint256 reward = 0; - uint256 toProver = 0; - uint256 burn = 0; - - if (_interimValues.isFeeCanonical) { - uint256 fees = uint256(publicInputs[10 + i * 2]); - if (fees > 0) { - // This is insanely expensive, and will be fixed as part of the general storage cost reduction. - // See #9826. - FeeHeader storage feeHeader = - _rollupStore.blocks[_interimValues.previousBlockNumber + 1 + i].feeHeader; - burn += feeHeader.congestionCost * feeHeader.manaUsed; - - reward += (fees - burn); - _addresses.feeJuicePortal.distributeFees(address(this), fees); - } - } - - if (_interimValues.isRewardDistributorCanonical) { - reward += _addresses.rewardDistributor.claim(address(this)); - } - - if (coinbase == address(0)) { - toProver = reward; - } else { - // @note We are getting value from the `proofClaim`, which are not cleared. - // So if someone is posting the proof before a new claim is made, - // the reward will calculated based on the previous values. - toProver = Math.mulDiv(reward, _rollupStore.proofClaim.basisPointFee, 10_000); - } - - uint256 toCoinbase = reward - toProver; - if (toCoinbase > 0) { - _addresses.asset.safeTransfer(coinbase, toCoinbase); - } - - _interimValues.totalProverReward += toProver; - _interimValues.totalBurn += burn; - } - - if (_interimValues.totalProverReward > 0) { - // If there is a bond-provider give him the reward, otherwise give it to the submitter. - address proofRewardRecipient = _rollupStore.proofClaim.bondProvider == address(0) - ? msg.sender - : _rollupStore.proofClaim.bondProvider; - _addresses.asset.safeTransfer(proofRewardRecipient, _interimValues.totalProverReward); - } - - if (_interimValues.totalBurn > 0) { - _addresses.asset.safeTransfer(_addresses.cuauhxicalli, _interimValues.totalBurn); - } - } - - return _interimValues.endBlockNumber; + return true; } /** @@ -158,7 +74,8 @@ library EpochProofLib { * own public inputs used for generating the proof vs the ones assembled * by this contract when verifying it. * - * @param _epochSize - The size of the epoch (to be promoted to a constant) + * @param _start - The start of the epoch (inclusive) + * @param _end - The end of the epoch (inclusive) * @param _args - Array of public inputs to the proof (previousArchive, endArchive, previousBlockHash, endBlockHash, endTimestamp, outHash, proverId) * @param _fees - Array of recipient-value pairs with fees to be distributed for the epoch * @param _blobPublicInputs- The blob public inputs for the proof @@ -166,15 +83,13 @@ library EpochProofLib { */ function getEpochProofPublicInputs( RollupStore storage _rollupStore, - uint256 _epochSize, + uint256 _start, + uint256 _end, bytes32[7] calldata _args, bytes32[] calldata _fees, bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) internal view returns (bytes32[] memory) { - uint256 previousBlockNumber = _rollupStore.tips.provenBlockNumber; - uint256 endBlockNumber = previousBlockNumber + _epochSize; - // Args are defined as an array because Solidity complains with "stack too deep" otherwise // 0 bytes32 _previousArchive, // 1 bytes32 _endArchive, @@ -188,24 +103,24 @@ library EpochProofLib { { // We do it this way to provide better error messages than passing along the storage values - bytes32 expectedPreviousArchive = _rollupStore.blocks[previousBlockNumber].archive; + bytes32 expectedPreviousArchive = _rollupStore.blocks[_start - 1].archive; require( expectedPreviousArchive == _args[0], Errors.Rollup__InvalidPreviousArchive(expectedPreviousArchive, _args[0]) ); - bytes32 expectedEndArchive = _rollupStore.blocks[endBlockNumber].archive; + bytes32 expectedEndArchive = _rollupStore.blocks[_end].archive; require( expectedEndArchive == _args[1], Errors.Rollup__InvalidArchive(expectedEndArchive, _args[1]) ); - bytes32 expectedPreviousBlockHash = _rollupStore.blocks[previousBlockNumber].blockHash; + bytes32 expectedPreviousBlockHash = _rollupStore.blocks[_start - 1].blockHash; require( expectedPreviousBlockHash == _args[2], Errors.Rollup__InvalidPreviousBlockHash(expectedPreviousBlockHash, _args[2]) ); - bytes32 expectedEndBlockHash = _rollupStore.blocks[endBlockNumber].blockHash; + bytes32 expectedEndBlockHash = _rollupStore.blocks[_end].blockHash; require( expectedEndBlockHash == _args[3], Errors.Rollup__InvalidBlockHash(expectedEndBlockHash, _args[3]) @@ -232,35 +147,36 @@ library EpochProofLib { // prover_id: Field, // blob_public_inputs: [BlockBlobPublicInputs; Constants.AZTEC_MAX_EPOCH_DURATION], // <--This will be reduced to 1 if/when we implement multi-opening for blob verification // } + { + // previous_archive.root: the previous archive tree root + publicInputs[0] = _args[0]; - // previous_archive.root: the previous archive tree root - publicInputs[0] = _args[0]; - - // previous_archive.next_available_leaf_index: the previous archive next available index - // normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed) - // but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N - publicInputs[1] = bytes32(previousBlockNumber + 1); + // previous_archive.next_available_leaf_index: the previous archive next available index + // normally this should be equal to the block number (since leaves are 0-indexed and blocks 1-indexed) + // but in yarn-project/merkle-tree/src/new_tree.ts we prefill the tree so that block N is in leaf N + publicInputs[1] = bytes32(_start); - // end_archive.root: the new archive tree root - publicInputs[2] = _args[1]; + // end_archive.root: the new archive tree root + publicInputs[2] = _args[1]; - // end_archive.next_available_leaf_index: the new archive next available index - publicInputs[3] = bytes32(endBlockNumber + 1); + // end_archive.next_available_leaf_index: the new archive next available index + publicInputs[3] = bytes32(_end + 1); - // previous_block_hash: the block hash just preceding this epoch - publicInputs[4] = _args[2]; + // previous_block_hash: the block hash just preceding this epoch + publicInputs[4] = _args[2]; - // end_block_hash: the last block hash in the epoch - publicInputs[5] = _args[3]; + // end_block_hash: the last block hash in the epoch + publicInputs[5] = _args[3]; - // end_timestamp: the timestamp of the last block in the epoch - publicInputs[6] = _args[4]; + // end_timestamp: the timestamp of the last block in the epoch + publicInputs[6] = _args[4]; - // end_block_number: last block number in the epoch - publicInputs[7] = bytes32(endBlockNumber); + // end_block_number: last block number in the epoch + publicInputs[7] = bytes32(_end); - // out_hash: root of this epoch's l2 to l1 message tree - publicInputs[8] = _args[5]; + // out_hash: root of this epoch's l2 to l1 message tree + publicInputs[8] = _args[5]; + } uint256 feesLength = Constants.AZTEC_MAX_EPOCH_DURATION * 2; // fees[9 to (9+feesLength-1)]: array of recipient-value pairs @@ -281,42 +197,46 @@ library EpochProofLib { publicInputs[offset] = _args[6]; offset += 1; - // blob_public_inputs - uint256 blobOffset = 0; - for (uint256 i = 0; i < _epochSize; i++) { - uint8 blobsInBlock = uint8(_blobPublicInputs[blobOffset++]); - for (uint256 j = 0; j < Constants.BLOBS_PER_BLOCK; j++) { - if (j < blobsInBlock) { - // z - publicInputs[offset++] = bytes32(_blobPublicInputs[blobOffset:blobOffset += 32]); - // y - (publicInputs[offset++], publicInputs[offset++], publicInputs[offset++]) = - bytes32ToBigNum(bytes32(_blobPublicInputs[blobOffset:blobOffset += 32])); - // To fit into 2 fields, the commitment is split into 31 and 17 byte numbers - // See yarn-project/foundation/src/blob/index.ts -> commitmentToFields() - // TODO: The below left pads, possibly inefficiently - // c[0] - publicInputs[offset++] = - bytes32(uint256(uint248(bytes31(_blobPublicInputs[blobOffset:blobOffset += 31])))); - // c[1] - publicInputs[offset++] = - bytes32(uint256(uint136(bytes17(_blobPublicInputs[blobOffset:blobOffset += 17])))); - } else { - offset += Constants.BLOB_PUBLIC_INPUTS; + { + // blob_public_inputs + uint256 blobOffset = 0; + for (uint256 i = 0; i < _end - _start + 1; i++) { + uint8 blobsInBlock = uint8(_blobPublicInputs[blobOffset++]); + for (uint256 j = 0; j < Constants.BLOBS_PER_BLOCK; j++) { + if (j < blobsInBlock) { + // z + publicInputs[offset++] = bytes32(_blobPublicInputs[blobOffset:blobOffset += 32]); + // y + (publicInputs[offset++], publicInputs[offset++], publicInputs[offset++]) = + bytes32ToBigNum(bytes32(_blobPublicInputs[blobOffset:blobOffset += 32])); + // To fit into 2 fields, the commitment is split into 31 and 17 byte numbers + // See yarn-project/foundation/src/blob/index.ts -> commitmentToFields() + // TODO: The below left pads, possibly inefficiently + // c[0] + publicInputs[offset++] = + bytes32(uint256(uint248(bytes31(_blobPublicInputs[blobOffset:blobOffset += 31])))); + // c[1] + publicInputs[offset++] = + bytes32(uint256(uint136(bytes17(_blobPublicInputs[blobOffset:blobOffset += 17])))); + } else { + offset += Constants.BLOB_PUBLIC_INPUTS; + } } } } - // the block proof is recursive, which means it comes with an aggregation object - // this snippet copies it into the public inputs needed for verification - // it also guards against empty _aggregationObject used with mocked proofs - uint256 aggregationLength = _aggregationObject.length / 32; - for (uint256 i = 0; i < Constants.AGGREGATION_OBJECT_LENGTH && i < aggregationLength; i++) { - bytes32 part; - assembly { - part := calldataload(add(_aggregationObject.offset, mul(i, 32))) + { + // the block proof is recursive, which means it comes with an aggregation object + // this snippet copies it into the public inputs needed for verification + // it also guards against empty _aggregationObject used with mocked proofs + uint256 aggregationLength = _aggregationObject.length / 32; + for (uint256 i = 0; i < Constants.AGGREGATION_OBJECT_LENGTH && i < aggregationLength; i++) { + bytes32 part; + assembly { + part := calldataload(add(_aggregationObject.offset, mul(i, 32))) + } + publicInputs[i + Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH] = part; } - publicInputs[i + Constants.ROOT_ROLLUP_PUBLIC_INPUTS_LENGTH] = part; } return publicInputs; diff --git a/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol b/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol deleted file mode 100644 index 4165263facba..000000000000 --- a/l1-contracts/src/core/libraries/RollupLibs/EpochProofQuoteLib.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {Slot, Epoch} from "@aztec/core/libraries/TimeLib.sol"; - -/** - * @notice Struct encompassing an epoch proof quote - * @param epochToProve - The epoch number to prove - * @param validUntilSlot - The deadline of the quote, denoted in L2 slots - * @param bondAmount - The size of the bond - * @param prover - The address of the prover - * @param basisPointFee - The fee measured in basis points - */ -struct EpochProofQuote { - Epoch epochToProve; - Slot validUntilSlot; - uint256 bondAmount; - address prover; - uint32 basisPointFee; -} - -/** - * @notice A signed quote for the epoch proof - * @param quote - The Epoch Proof Quote - * @param signature - A signature on the quote - */ -struct SignedEpochProofQuote { - EpochProofQuote quote; - Signature signature; -} - -library EpochProofQuoteLib { - bytes32 public constant EPOCH_PROOF_QUOTE_TYPEHASH = keccak256( - "EpochProofQuote(uint256 epochToProve,uint256 validUntilSlot,uint256 bondAmount,address prover,uint32 basisPointFee)" - ); - - function hash(EpochProofQuote memory _quote) internal pure returns (bytes32) { - return keccak256( - abi.encode( - EPOCH_PROOF_QUOTE_TYPEHASH, - _quote.epochToProve, - _quote.validUntilSlot, - _quote.bondAmount, - _quote.prover, - _quote.basisPointFee - ) - ); - } -} diff --git a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol index 3676a5d13c6b..aeddc72abe6f 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ExtRollupLib.sol @@ -2,21 +2,11 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; -import {BlockLog, RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; -import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; -import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {DataStructures} from "./../DataStructures.sol"; -import {Slot, Epoch} from "./../TimeLib.sol"; +import {RollupStore, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; + +import {BlockLog, RollupStore} from "@aztec/core/interfaces/IRollup.sol"; import {BlobLib} from "./BlobLib.sol"; -import { - EpochProofLib, - SubmitEpochRootProofAddresses, - SubmitEpochRootProofInterimValues -} from "./EpochProofLib.sol"; -import {SignedEpochProofQuote} from "./EpochProofQuoteLib.sol"; +import {EpochProofLib} from "./EpochProofLib.sol"; import {FeeMath, ManaBaseFeeComponents, FeeHeader, L1FeeData} from "./FeeMath.sol"; import {HeaderLib, Header} from "./HeaderLib.sol"; import {ValidationLib, ValidateHeaderArgs} from "./ValidationLib.sol"; @@ -24,30 +14,6 @@ import {ValidationLib, ValidateHeaderArgs} from "./ValidationLib.sol"; // instead of a few smaller ones. library ExtRollupLib { - function submitEpochRootProof( - RollupStore storage _rollupStore, - SubmitEpochRootProofArgs calldata _args, - SubmitEpochRootProofInterimValues memory _interimValues, - IProofCommitmentEscrow _proofCommitmentEscrow, - IFeeJuicePortal _feeJuicePortal, - IRewardDistributor _rewardDistributor, - IERC20 _asset, - address _cuauhxicalli - ) external returns (uint256) { - return EpochProofLib.submitEpochRootProof( - _rollupStore, - _args, - _interimValues, - SubmitEpochRootProofAddresses({ - proofCommitmentEscrow: _proofCommitmentEscrow, - feeJuicePortal: _feeJuicePortal, - rewardDistributor: _rewardDistributor, - asset: _asset, - cuauhxicalli: _cuauhxicalli - }) - ); - } - function validateHeaderForSubmissionBase( ValidateHeaderArgs memory _args, mapping(uint256 blockNumber => BlockLog log) storage _blocks @@ -55,52 +21,36 @@ library ExtRollupLib { ValidationLib.validateHeaderForSubmissionBase(_args, _blocks); } - function validateEpochProofRightClaimAtTime( - Slot _currentSlot, - address _currentProposer, - Epoch _epochToProve, - uint256 _posInEpoch, - SignedEpochProofQuote calldata _quote, - bytes32 _digest, - DataStructures.EpochProofClaim storage _proofClaim, - uint256 _claimDurationInL2Slots, - uint256 _proofCommitmentMinBondAmountInTst, - IProofCommitmentEscrow _proofCommitmentEscrow - ) external view { - ValidationLib.validateEpochProofRightClaimAtTime( - _currentSlot, - _currentProposer, - _epochToProve, - _posInEpoch, - _quote, - _digest, - _proofClaim, - _claimDurationInL2Slots, - _proofCommitmentMinBondAmountInTst, - _proofCommitmentEscrow - ); + function verifyEpochRootProof( + RollupStore storage _rollupStore, + SubmitEpochRootProofArgs calldata _args + ) external view returns (bool) { + return EpochProofLib.verifyEpochRootProof(_rollupStore, _args); } function getManaBaseFeeComponentsAt( FeeHeader storage _parentFeeHeader, L1FeeData memory _fees, + uint256 _provingCostPerMana, uint256 _feeAssetPrice, uint256 _epochDuration ) external view returns (ManaBaseFeeComponents memory) { - return - FeeMath.getManaBaseFeeComponentsAt(_parentFeeHeader, _fees, _feeAssetPrice, _epochDuration); + return FeeMath.getManaBaseFeeComponentsAt( + _parentFeeHeader, _fees, _provingCostPerMana, _feeAssetPrice, _epochDuration + ); } function getEpochProofPublicInputs( RollupStore storage _rollupStore, - uint256 _epochSize, + uint256 _start, + uint256 _end, bytes32[7] calldata _args, bytes32[] calldata _fees, bytes calldata _blobPublicInputs, bytes calldata _aggregationObject ) external view returns (bytes32[] memory) { return EpochProofLib.getEpochProofPublicInputs( - _rollupStore, _epochSize, _args, _fees, _blobPublicInputs, _aggregationObject + _rollupStore, _start, _end, _args, _fees, _blobPublicInputs, _aggregationObject ); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol index ef3a27ffafbe..98bc2d83e102 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/FeeMath.sol @@ -62,6 +62,7 @@ library FeeMath { function getManaBaseFeeComponentsAt( FeeHeader storage _parentFeeHeader, L1FeeData memory _fees, + uint256 _provingCostPerMana, uint256 _feeAssetPrice, uint256 _epochDuration ) internal view returns (ManaBaseFeeComponents memory) { @@ -74,10 +75,9 @@ library FeeMath { uint256 gasUsed = L1_GAS_PER_BLOCK_PROPOSED + 3 * GAS_PER_BLOB_POINT_EVALUATION + L1_GAS_PER_EPOCH_VERIFIED / _epochDuration; uint256 gasCost = Math.mulDiv(gasUsed, _fees.baseFee, MANA_TARGET, Math.Rounding.Ceil); - uint256 provingCost = FeeMath.provingCostPerMana(_parentFeeHeader.provingCostPerManaNumerator); uint256 congestionMultiplier_ = congestionMultiplier(excessMana); - uint256 total = dataCost + gasCost + provingCost; + uint256 total = dataCost + gasCost + _provingCostPerMana; uint256 congestionCost = Math.mulDiv( total, congestionMultiplier_, MINIMUM_CONGESTION_MULTIPLIER, Math.Rounding.Floor ) - total; @@ -87,7 +87,7 @@ library FeeMath { return ManaBaseFeeComponents({ dataCost: Math.mulDiv(dataCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), gasCost: Math.mulDiv(gasCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), - provingCost: Math.mulDiv(provingCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), + provingCost: Math.mulDiv(_provingCostPerMana, _feeAssetPrice, 1e9, Math.Rounding.Ceil), congestionCost: Math.mulDiv(congestionCost, _feeAssetPrice, 1e9, Math.Rounding.Ceil), congestionMultiplier: congestionMultiplier_ }); @@ -127,10 +127,6 @@ library FeeMath { return 0; } - function provingCostPerMana(uint256 _numerator) internal pure returns (uint256) { - return fakeExponential(MINIMUM_PROVING_COST_PER_MANA, _numerator, PROVING_UPDATE_FRACTION); - } - function feeAssetPriceModifier(uint256 _numerator) internal pure returns (uint256) { return fakeExponential(MINIMUM_FEE_ASSET_PRICE, _numerator, FEE_ASSET_PRICE_UPDATE_FRACTION); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol b/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol index f05eda331dda..f0610f0576c1 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/IntRollupLib.sol @@ -2,17 +2,11 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {EpochProofQuoteLib, EpochProofQuote} from "./EpochProofQuoteLib.sol"; - import {FeeMath, ManaBaseFeeComponents, FeeHeader, MANA_TARGET} from "./FeeMath.sol"; // We are using this library such that we can more easily "link" just a larger external library // instead of a few smaller ones. library IntRollupLib { - function computeQuoteHash(EpochProofQuote memory _quote) internal pure returns (bytes32) { - return EpochProofQuoteLib.hash(_quote); - } - function summedBaseFee(ManaBaseFeeComponents memory _components) internal pure returns (uint256) { return FeeMath.summedBaseFee(_components); } diff --git a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol index 72931ba060e3..2d357a22eff9 100644 --- a/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol +++ b/l1-contracts/src/core/libraries/RollupLibs/ValidationLib.sol @@ -3,14 +3,11 @@ pragma solidity >=0.8.27; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {BlockLog} from "@aztec/core/interfaces/IRollup.sol"; -import {SignatureLib} from "@aztec/core/libraries/crypto/SignatureLib.sol"; import {DataStructures} from "./../DataStructures.sol"; import {Errors} from "./../Errors.sol"; -import {Timestamp, Slot, Epoch} from "./../TimeLib.sol"; +import {Timestamp, Slot} from "./../TimeLib.sol"; import {TimeLib} from "./../TimeLib.sol"; -import {SignedEpochProofQuote} from "./EpochProofQuoteLib.sol"; import {Header} from "./HeaderLib.sol"; struct ValidateHeaderArgs { @@ -95,64 +92,4 @@ library ValidationLib { ); } } - - function validateEpochProofRightClaimAtTime( - Slot _currentSlot, - address _currentProposer, - Epoch _epochToProve, - uint256 _posInEpoch, - SignedEpochProofQuote calldata _quote, - bytes32 _digest, - DataStructures.EpochProofClaim storage _proofClaim, - uint256 _claimDurationInL2Slots, - uint256 _proofCommitmentMinBondAmountInTst, - IProofCommitmentEscrow _proofCommitmentEscrow - ) internal view { - SignatureLib.verify(_quote.signature, _quote.quote.prover, _digest); - - require( - _quote.quote.validUntilSlot >= _currentSlot, - Errors.Rollup__QuoteExpired(_currentSlot, _quote.quote.validUntilSlot) - ); - - require( - _quote.quote.basisPointFee <= 10_000, - Errors.Rollup__InvalidBasisPointFee(_quote.quote.basisPointFee) - ); - - require( - _currentProposer == address(0) || _currentProposer == msg.sender, - Errors.ValidatorSelection__InvalidProposer(_currentProposer, msg.sender) - ); - - require( - _quote.quote.epochToProve == _epochToProve, - Errors.Rollup__NotClaimingCorrectEpoch(_epochToProve, _quote.quote.epochToProve) - ); - - require( - _posInEpoch < _claimDurationInL2Slots, - Errors.Rollup__NotInClaimPhase(_posInEpoch, _claimDurationInL2Slots) - ); - - // if the epoch to prove is not the one that has been claimed, - // then whatever is in the proofClaim is stale - require( - _proofClaim.epochToProve != _epochToProve || _proofClaim.proposerClaimant == address(0), - Errors.Rollup__ProofRightAlreadyClaimed() - ); - - require( - _quote.quote.bondAmount >= _proofCommitmentMinBondAmountInTst, - Errors.Rollup__InsufficientBondAmount( - _proofCommitmentMinBondAmountInTst, _quote.quote.bondAmount - ) - ); - - uint256 availableFundsInEscrow = _proofCommitmentEscrow.deposits(_quote.quote.prover); - require( - _quote.quote.bondAmount <= availableFundsInEscrow, - Errors.Rollup__InsufficientFundsInEscrow(_quote.quote.bondAmount, availableFundsInEscrow) - ); - } } diff --git a/l1-contracts/src/governance/RewardDistributor.sol b/l1-contracts/src/governance/RewardDistributor.sol index 759920b88252..1f746fa7b13b 100644 --- a/l1-contracts/src/governance/RewardDistributor.sol +++ b/l1-contracts/src/governance/RewardDistributor.sol @@ -56,6 +56,26 @@ contract RewardDistributor is IRewardDistributor, Ownable { return reward; } + function claimBlockRewards(address _to, uint256 _blocks) + external + override(IRewardDistributor) + returns (uint256) + { + require( + msg.sender == canonicalRollup(), + Errors.RewardDistributor__InvalidCaller(msg.sender, canonicalRollup()) + ); + + uint256 bal = ASSET.balanceOf(address(this)); + uint256 reward = bal > BLOCK_REWARD * _blocks ? BLOCK_REWARD * _blocks : bal; + + if (reward > 0) { + ASSET.safeTransfer(_to, reward); + } + + return reward; + } + function canonicalRollup() public view override(IRewardDistributor) returns (address) { return registry.getRollup(); } diff --git a/l1-contracts/src/governance/interfaces/IRewardDistributor.sol b/l1-contracts/src/governance/interfaces/IRewardDistributor.sol index 5b4c9c30254c..06a8bb766871 100644 --- a/l1-contracts/src/governance/interfaces/IRewardDistributor.sol +++ b/l1-contracts/src/governance/interfaces/IRewardDistributor.sol @@ -8,5 +8,6 @@ interface IRewardDistributor { function updateRegistry(IRegistry _registry) external; function claim(address _to) external returns (uint256); + function claimBlockRewards(address _to, uint256 _amount) external returns (uint256); function canonicalRollup() external view returns (address); } diff --git a/l1-contracts/test/CheatingRollup.t.sol b/l1-contracts/test/CheatingRollup.t.sol new file mode 100644 index 000000000000..7a48a1abd79a --- /dev/null +++ b/l1-contracts/test/CheatingRollup.t.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {DecoderBase} from "./base/DecoderBase.sol"; + +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Math} from "@oz/utils/math/Math.sol"; + +import {Registry} from "@aztec/governance/Registry.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; +import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; +import {Rollup} from "./harnesses/Rollup.sol"; +import {IRollupCore, BlockLog, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; +import {NaiveMerkle} from "./merkle/Naive.sol"; +import {MerkleTestUtil} from "./merkle/TestUtil.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {TestConstants} from "./harnesses/TestConstants.sol"; +import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; +import { + ProposeArgs, OracleInput, ProposeLib +} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; + +import { + Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib +} from "@aztec/core/libraries/TimeLib.sol"; + +import {RollupBase, IInstance} from "./base/RollupBase.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. + */ +contract CheatingRollupTest is RollupBase { + using SlotLib for Slot; + using EpochLib for Epoch; + using ProposeLib for ProposeArgs; + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + + Registry internal registry; + TestERC20 internal testERC20; + FeeJuicePortal internal feeJuicePortal; + RewardDistributor internal rewardDistributor; + + uint256 internal SLOT_DURATION; + uint256 internal EPOCH_DURATION; + + constructor() { + TimeLib.initialize( + block.timestamp, TestConstants.AZTEC_SLOT_DURATION, TestConstants.AZTEC_EPOCH_DURATION + ); + SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION; + EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; + } + + /** + * @notice Set up the contracts needed for the tests with time aligned to the provided block name + */ + modifier setUpFor(string memory _name) { + { + testERC20 = new TestERC20("test", "TEST", address(this)); + + DecoderBase.Full memory full = load(_name); + uint256 slotNumber = full.block.decodedHeader.globalVariables.slotNumber; + uint256 initialTime = + full.block.decodedHeader.globalVariables.timestamp - slotNumber * SLOT_DURATION; + vm.warp(initialTime); + } + + registry = new Registry(address(this)); + feeJuicePortal = new FeeJuicePortal( + address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) + ); + testERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.initialize(); + rewardDistributor = new RewardDistributor(testERC20, registry, address(this)); + testERC20.mint(address(rewardDistributor), 1e6 ether); + + rollup = IInstance( + address( + new Rollup( + feeJuicePortal, rewardDistributor, testERC20, bytes32(0), bytes32(0), address(this) + ) + ) + ); + inbox = Inbox(address(rollup.INBOX())); + outbox = Outbox(address(rollup.OUTBOX())); + + registry.upgrade(address(rollup)); + + merkleTestUtil = new MerkleTestUtil(); + _; + } + + function warpToL2Slot(uint256 _slot) public { + vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(_slot)))); + } + + // BRUH, what is this stuff? + function testBlocksWithAssumeProven() public setUpFor("mixed_block_1") { + rollup.setAssumeProvenThroughBlockNumber(1); + assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); + + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", 2); + + assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 1, "Invalid proven block number"); + } + + function testSetAssumeProvenAfterBlocksProcessed() public setUpFor("mixed_block_1") { + assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); + + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", 2); + rollup.setAssumeProvenThroughBlockNumber(1); + + assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 1, "Invalid proven block number"); + } +} diff --git a/l1-contracts/test/MultiProof.t.sol b/l1-contracts/test/MultiProof.t.sol new file mode 100644 index 000000000000..28e58ea7222d --- /dev/null +++ b/l1-contracts/test/MultiProof.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2024 Aztec Labs. +pragma solidity >=0.8.27; + +import {DecoderBase} from "./base/DecoderBase.sol"; + +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; + +import {Registry} from "@aztec/governance/Registry.sol"; +import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {TestConstants} from "./harnesses/TestConstants.sol"; +import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; +import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; + +import { + Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib +} from "@aztec/core/libraries/TimeLib.sol"; + +import {Rollup, Config} from "@aztec/core/Rollup.sol"; +import {Strings} from "@oz/utils/Strings.sol"; +import {Errors} from "@aztec/core/libraries/Errors.sol"; + +import {RollupBase, IInstance} from "./base/RollupBase.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. + */ +contract MultiProofTest is RollupBase { + using SlotLib for Slot; + using EpochLib for Epoch; + using ProposeLib for ProposeArgs; + using TimeLib for Timestamp; + using TimeLib for Slot; + using TimeLib for Epoch; + + Registry internal registry; + TestERC20 internal testERC20; + FeeJuicePortal internal feeJuicePortal; + RewardDistributor internal rewardDistributor; + + uint256 internal SLOT_DURATION; + uint256 internal EPOCH_DURATION; + + constructor() { + TimeLib.initialize( + block.timestamp, TestConstants.AZTEC_SLOT_DURATION, TestConstants.AZTEC_EPOCH_DURATION + ); + SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION; + EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION; + } + + /** + * @notice Set up the contracts needed for the tests with time aligned to the provided block name + */ + modifier setUpFor(string memory _name) { + { + testERC20 = new TestERC20("test", "TEST", address(this)); + + DecoderBase.Full memory full = load(_name); + uint256 slotNumber = full.block.decodedHeader.globalVariables.slotNumber; + uint256 initialTime = + full.block.decodedHeader.globalVariables.timestamp - slotNumber * SLOT_DURATION; + vm.warp(initialTime); + } + + registry = new Registry(address(this)); + feeJuicePortal = new FeeJuicePortal( + address(registry), address(testERC20), bytes32(Constants.FEE_JUICE_ADDRESS) + ); + testERC20.mint(address(feeJuicePortal), Constants.FEE_JUICE_INITIAL_MINT); + feeJuicePortal.initialize(); + rewardDistributor = new RewardDistributor(testERC20, registry, address(this)); + testERC20.mint(address(rewardDistributor), 1e6 ether); + + rollup = IInstance( + address( + new Rollup( + feeJuicePortal, + rewardDistributor, + testERC20, + bytes32(0), + bytes32(0), + address(this), + Config({ + aztecSlotDuration: TestConstants.AZTEC_SLOT_DURATION, + aztecEpochDuration: TestConstants.AZTEC_EPOCH_DURATION, + targetCommitteeSize: TestConstants.AZTEC_TARGET_COMMITTEE_SIZE, + aztecProofSubmissionWindow: TestConstants.AZTEC_PROOF_SUBMISSION_WINDOW, + minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, + slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, + slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE + }) + ) + ) + ); + + registry.upgrade(address(rollup)); + + _; + } + + function warpToL2Slot(uint256 _slot) public { + vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(_slot)))); + } + + function logStatus() public { + uint256 provenBlockNumber = rollup.getProvenBlockNumber(); + uint256 pendingBlockNumber = rollup.getPendingBlockNumber(); + emit log_named_uint("proven block number", provenBlockNumber); + emit log_named_uint("pending block number", pendingBlockNumber); + + address[2] memory provers = [address(bytes20("lasse")), address(bytes20("mitch"))]; + address sequencer = address(bytes20("sequencer")); + + emit log_named_decimal_uint("sequencer rewards", rollup.getSequencerRewards(sequencer), 18); + emit log_named_decimal_uint( + "prover rewards", rollup.getCollectiveProverRewardsForEpoch(Epoch.wrap(0)), 18 + ); + + for (uint256 i = 0; i < provers.length; i++) { + for (uint256 j = 1; j <= provenBlockNumber; j++) { + bool hasSubmitted = rollup.getHasSubmitted(Epoch.wrap(0), j, provers[i]); + if (hasSubmitted) { + emit log_named_string( + string.concat("prover has submitted proof up till block ", Strings.toString(j)), + string(abi.encode(provers[i])) + ); + } + } + emit log_named_decimal_uint( + string.concat("prover ", string(abi.encode(provers[i])), " rewards"), + rollup.getSpecificProverRewardsForEpoch(Epoch.wrap(0), provers[i]), + 18 + ); + } + } + + function testMultiProof() public setUpFor("mixed_block_1") { + _proposeBlock("mixed_block_1", 1, 15e6); + _proposeBlock("mixed_block_2", 2, 15e6); + + assertEq(rollup.getProvenBlockNumber(), 0, "Block already proven"); + + string memory name = "mixed_block_"; + _proveBlocks(name, 1, 1, address(bytes20("lasse"))); + _proveBlocks(name, 1, 1, address(bytes20("mitch"))); + _proveBlocks(name, 1, 2, address(bytes20("mitch"))); + + logStatus(); + + assertEq(rollup.getProvenBlockNumber(), 2, "Block not proven"); + } + + function testNoHolesInProvenBlocks() public setUpFor("mixed_block_1") { + _proposeBlock("mixed_block_1", 1, 15e6); + _proposeBlock("mixed_block_2", TestConstants.AZTEC_EPOCH_DURATION + 1, 15e6); + + string memory name = "mixed_block_"; + _proveBlocksFail( + name, + 2, + 2, + address(bytes20("lasse")), + abi.encodeWithSelector(Errors.Rollup__StartIsNotBuildingOnProven.selector) + ); + } + + function testProofsAreInOneEpoch() public setUpFor("mixed_block_1") { + _proposeBlock("mixed_block_1", 1, 15e6); + _proposeBlock("mixed_block_2", TestConstants.AZTEC_EPOCH_DURATION + 1, 15e6); + + string memory name = "mixed_block_"; + _proveBlocksFail( + name, + 1, + 2, + address(bytes20("lasse")), + abi.encodeWithSelector(Errors.Rollup__StartAndEndNotSameEpoch.selector, 0, 1) + ); + } +} diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index 3439bd957cfc..8d2224eed0d0 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -2,15 +2,11 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {DecoderBase} from "./decoders/Base.sol"; +import {DecoderBase} from "./base/DecoderBase.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import { - EpochProofQuote, - SignedEpochProofQuote -} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; import {Math} from "@oz/utils/math/Math.sol"; import {Registry} from "@aztec/governance/Registry.sol"; @@ -19,7 +15,6 @@ import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; import {Errors} from "@aztec/core/libraries/Errors.sol"; import {Rollup} from "./harnesses/Rollup.sol"; import {IRollupCore, BlockLog, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {NaiveMerkle} from "./merkle/Naive.sol"; import {MerkleTestUtil} from "./merkle/TestUtil.sol"; @@ -35,13 +30,15 @@ import { Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib } from "@aztec/core/libraries/TimeLib.sol"; +import {RollupBase, IInstance} from "./base/RollupBase.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. */ -contract RollupTest is DecoderBase { +contract RollupTest is RollupBase { using SlotLib for Slot; using EpochLib for Epoch; using ProposeLib for ProposeArgs; @@ -50,21 +47,9 @@ contract RollupTest is DecoderBase { using TimeLib for Epoch; Registry internal registry; - Inbox internal inbox; - Outbox internal outbox; - Rollup internal rollup; - MerkleTestUtil internal merkleTestUtil; TestERC20 internal testERC20; FeeJuicePortal internal feeJuicePortal; - IProofCommitmentEscrow internal proofCommitmentEscrow; RewardDistributor internal rewardDistributor; - Signature[] internal signatures; - - EpochProofQuote internal quote; - SignedEpochProofQuote internal signedQuote; - - uint256 internal privateKey; - address internal signer; uint256 internal SLOT_DURATION; uint256 internal EPOCH_DURATION; @@ -100,35 +85,19 @@ contract RollupTest is DecoderBase { rewardDistributor = new RewardDistributor(testERC20, registry, address(this)); testERC20.mint(address(rewardDistributor), 1e6 ether); - rollup = new Rollup( - feeJuicePortal, rewardDistributor, testERC20, bytes32(0), bytes32(0), address(this) + rollup = IInstance( + address( + new Rollup( + feeJuicePortal, rewardDistributor, testERC20, bytes32(0), bytes32(0), address(this) + ) + ) ); inbox = Inbox(address(rollup.INBOX())); outbox = Outbox(address(rollup.OUTBOX())); - proofCommitmentEscrow = IProofCommitmentEscrow(address(rollup.PROOF_COMMITMENT_ESCROW())); registry.upgrade(address(rollup)); merkleTestUtil = new MerkleTestUtil(); - - privateKey = 0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234; - signer = vm.addr(privateKey); - uint256 bond = rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(); - quote = EpochProofQuote({ - epochToProve: Epoch.wrap(0), - validUntilSlot: Slot.wrap(1), - bondAmount: bond, - prover: signer, - basisPointFee: 500 - }); - signedQuote = _quoteToSignedQuote(quote); - - testERC20.mint(signer, bond * 10); - vm.prank(signer); - testERC20.approve(address(proofCommitmentEscrow), bond * 10); - vm.prank(signer); - proofCommitmentEscrow.deposit(bond * 10); - _; } @@ -136,373 +105,77 @@ contract RollupTest is DecoderBase { vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(_slot)))); } - function getBlobPublicInputs(bytes calldata _blobsInput) - public - pure - returns (bytes memory blobPublicInputs) - { - uint8 numBlobs = uint8(_blobsInput[0]); - blobPublicInputs = abi.encodePacked(numBlobs, blobPublicInputs); - for (uint256 i = 0; i < numBlobs; i++) { - // Add 1 for the numBlobs prefix - uint256 blobInputStart = i * 192 + 1; - // We want to extract the bytes we use for public inputs: - // * input[32:64] - z - // * input[64:96] - y - // * input[96:144] - commitment C - // Out of 192 bytes per blob. - blobPublicInputs = - abi.encodePacked(blobPublicInputs, _blobsInput[blobInputStart + 32:blobInputStart + 144]); - } - } - - function getBlobPublicInputsHash(bytes calldata _blobPublicInputs) - public - pure - returns (bytes32 publicInputsHash) - { - uint8 numBlobs = uint8(_blobPublicInputs[0]); - publicInputsHash = sha256(abi.encodePacked(_blobPublicInputs[1:1 + numBlobs * 112])); - } - - function testClaimInTheFuture(uint256 _futureSlot) public setUpFor("mixed_block_1") { - uint256 futureSlot = bound(_futureSlot, 1, 1e20); - _testBlock("mixed_block_1", false, 1); - - rollup.validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), signedQuote); - - Timestamp t = rollup.getTimestampForSlot(quote.validUntilSlot + Slot.wrap(futureSlot)); - vm.expectRevert( - abi.encodeWithSelector( - Errors.Rollup__QuoteExpired.selector, - Slot.wrap(futureSlot) + quote.validUntilSlot, - signedQuote.quote.validUntilSlot - ) - ); - rollup.validateEpochProofRightClaimAtTime(t, signedQuote); - } - - function testClaimableEpoch(uint256 epochForMixedBlock) public setUpFor("mixed_block_1") { - epochForMixedBlock = bound(epochForMixedBlock, 1, 10); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - assertEq(rollup.getClaimableEpoch(), 0, "Invalid claimable epoch"); - - quote.epochToProve = Epoch.wrap(epochForMixedBlock); - quote.validUntilSlot = Slot.wrap(epochForMixedBlock * EPOCH_DURATION + 1); - signedQuote = _quoteToSignedQuote(quote); - - _testBlock("mixed_block_1", false, epochForMixedBlock * EPOCH_DURATION); - assertEq(rollup.getClaimableEpoch(), Epoch.wrap(epochForMixedBlock), "Invalid claimable epoch"); - - rollup.claimEpochProofRight(signedQuote); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.getClaimableEpoch(); - } - - function testClaimWithNothingToProve() public setUpFor("mixed_block_1") { - assertEq(rollup.getCurrentSlot(), 0, "genesis slot should be zero"); - - // sanity check that proven/pending tip are at genesis - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(signedQuote); - - 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(signedQuote); - } - - function testClaimWithWrongEpoch() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - quote.epochToProve = Epoch.wrap(1); - signedQuote = _quoteToSignedQuote(quote); - - vm.expectRevert( - abi.encodeWithSelector( - Errors.Rollup__NotClaimingCorrectEpoch.selector, 0, signedQuote.quote.epochToProve - ) - ); - rollup.claimEpochProofRight(signedQuote); - } - - function testClaimWithInsufficientBond() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - - quote.bondAmount = 0; - signedQuote = _quoteToSignedQuote(quote); - - vm.expectRevert( - abi.encodeWithSelector( - Errors.Rollup__InsufficientBondAmount.selector, - rollup.PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST(), - signedQuote.quote.bondAmount - ) - ); - rollup.claimEpochProofRight(signedQuote); - } - - function testClaimPastValidUntil() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - - quote.validUntilSlot = Slot.wrap(0); - signedQuote = _quoteToSignedQuote(quote); - - vm.expectRevert( - abi.encodeWithSelector( - Errors.Rollup__QuoteExpired.selector, 1, signedQuote.quote.validUntilSlot - ) - ); - rollup.claimEpochProofRight(signedQuote); - } - - function testClaimSimple() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - - vm.expectEmit(true, true, true, true); - emit IRollupCore.ProofRightClaimed( - quote.epochToProve, quote.prover, address(this), quote.bondAmount, Slot.wrap(1) - ); - rollup.claimEpochProofRight(signedQuote); - - DataStructures.EpochProofClaim memory epochProofClaim = rollup.getProofClaim(); - assertEq(epochProofClaim.epochToProve, signedQuote.quote.epochToProve, "Invalid epoch to prove"); - assertEq( - epochProofClaim.basisPointFee, signedQuote.quote.basisPointFee, "Invalid basis point fee" - ); - assertEq(epochProofClaim.bondAmount, signedQuote.quote.bondAmount, "Invalid bond amount"); - assertEq(epochProofClaim.bondProvider, quote.prover, "Invalid bond provider"); - assertEq(epochProofClaim.proposerClaimant, address(this), "Invalid proposer claimant"); - assertEq( - proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 9, "Invalid escrow balance" - ); - } - - function testProofReleasesBond() public setUpFor("mixed_block_1") { - DecoderBase.Data memory data = load("mixed_block_1").block; - bytes memory header = data.header; - bytes32 proverId = bytes32(uint256(42)); - - // We jump to the time of the block. (unless it is in the past) - vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); - - header = _updateHeaderBaseFee(header); - skipBlobCheck(address(rollup)); - ProposeArgs memory args = ProposeArgs({ - header: header, - archive: data.archive, - blockHash: data.blockHash, - oracleInput: OracleInput(0, 0), - txHashes: new bytes32[](0) - }); - rollup.propose(args, signatures, data.body, data.blobInputs); - - quote.epochToProve = Epoch.wrap(1); - quote.validUntilSlot = Epoch.wrap(2).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - rollup.claimEpochProofRight(signedQuote); - BlockLog memory blockLog = rollup.getBlock(0); - - assertEq( - proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 9, "Invalid escrow balance" - ); - - _submitEpochProof( - rollup, - 1, - blockLog.archive, - data.archive, - blockLog.blockHash, - data.blockHash, - proverId, - this.getBlobPublicInputs(data.blobInputs) - ); - - assertEq( - proofCommitmentEscrow.deposits(quote.prover), quote.bondAmount * 10, "Invalid escrow balance" - ); - } - - function testMissingProofSlashesBond(uint256 _slotToHit) public setUpFor("mixed_block_1") { - Slot lower = rollup.getCurrentSlot() + Slot.wrap(2 * EPOCH_DURATION); - Slot upper = Slot.wrap( - (type(uint256).max - Timestamp.unwrap(rollup.getGenesisTime())) / rollup.getSlotDuration() - ); - Slot slotToHit = Slot.wrap(bound(_slotToHit, lower.unwrap(), upper.unwrap())); - - _testBlock("mixed_block_1", false, 1); - rollup.claimEpochProofRight(signedQuote); - warpToL2Slot(slotToHit.unwrap()); - rollup.prune(); - _testBlock("mixed_block_1", true, slotToHit.unwrap()); - - assertEq( - proofCommitmentEscrow.deposits(quote.prover), 9 * quote.bondAmount, "Invalid escrow balance" - ); - } - - function testClaimTwice() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - quote.validUntilSlot = Epoch.wrap(1e9).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - - rollup.claimEpochProofRight(signedQuote); - - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(signedQuote); - - warpToL2Slot(2); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector)); - rollup.claimEpochProofRight(signedQuote); + function testPruneAfterPartial() public setUpFor("mixed_block_1") { + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", 2); - // warp to epoch 1 - warpToL2Slot(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(signedQuote); - - // still nothing to prune + warpToL2Slot(rollup.getProofSubmissionWindow()); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); rollup.prune(); - } - - function testClaimOutsideClaimPhase() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - quote.validUntilSlot = Epoch.wrap(1e9).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - warpToL2Slot(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(signedQuote); - } - - function testNoPruneWhenClaimExists() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - - quote.validUntilSlot = Epoch.wrap(2).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - warpToL2Slot(EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - - rollup.claimEpochProofRight(signedQuote); - - warpToL2Slot(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, 1); - - quote.validUntilSlot = Epoch.wrap(2).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - - warpToL2Slot(EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - - rollup.claimEpochProofRight(signedQuote); - - warpToL2Slot(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(signedQuote); + _proveBlocks("mixed_block_", 1, 1, address(this)); + warpToL2Slot(rollup.getProofSubmissionWindow() + 1); rollup.prune(); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector)); - rollup.claimEpochProofRight(signedQuote); + assertEq(rollup.getPendingBlockNumber(), 1); + assertEq(rollup.getProvenBlockNumber(), 1); } - function testClaimAfterPrune() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - - quote.validUntilSlot = Epoch.wrap(3).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - - warpToL2Slot(EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); + function testPrune() public setUpFor("mixed_block_1") { + _proposeBlock("mixed_block_1", 1); - rollup.claimEpochProofRight(signedQuote); + assertEq(inbox.inProgress(), 3, "Invalid in progress"); - warpToL2Slot(EPOCH_DURATION * 3); + // @note Fetch the inbox root of block 2. This should be frozen when block 1 is proposed. + // Even if we end up reverting block 1, we should still see the same root in the inbox. + bytes32 inboxRoot2 = inbox.getRoot(2); - rollup.prune(); + BlockLog memory blockLog = rollup.getBlock(1); + Slot prunableAt = blockLog.slotNumber + Epoch.wrap(2).toSlots(); - _testBlock("mixed_block_1", false, Epoch.wrap(3).toSlots().unwrap()); + Timestamp timeOfPrune = rollup.getTimestampForSlot(prunableAt); + vm.warp(Timestamp.unwrap(timeOfPrune)); - quote.epochToProve = Epoch.wrap(3); - signedQuote = _quoteToSignedQuote(quote); + assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - vm.expectEmit(true, true, true, true); - emit IRollupCore.ProofRightClaimed( - quote.epochToProve, quote.prover, address(this), quote.bondAmount, Epoch.wrap(3).toSlots() - ); - rollup.claimEpochProofRight(signedQuote); - } + // @note Get the root and min height that we have in the outbox. + // We read it directly in storage because it is not yet proven, so the getter will give (0, 0). + // The values are stored such that we can check that after pruning, and inserting a new block, + // we will override it. + bytes32 rootMixed = vm.load(address(outbox), keccak256(abi.encode(1, 0))); + uint256 minHeightMixed = + uint256(vm.load(address(outbox), bytes32(uint256(keccak256(abi.encode(1, 0))) + 1))); - function testPruneWhenNoProofClaim() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - warpToL2Slot(EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); - rollup.prune(); + assertNotEq(rootMixed, bytes32(0), "Invalid root"); + assertNotEq(minHeightMixed, 0, "Invalid min height"); - warpToL2Slot(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; - bytes memory body = data.body; - - // We jump to the time of the block. (unless it is in the past) - vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); + assertEq(inbox.inProgress(), 3, "Invalid in progress"); + assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - header = _updateHeaderBaseFee(header); + // @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. + _proposeBlock("empty_block_1", prunableAt.unwrap()); - skipBlobCheck(address(rollup)); - ProposeArgs memory args = ProposeArgs({ - header: header, - archive: data.archive, - blockHash: data.blockHash, - oracleInput: OracleInput(0, 0), - txHashes: new bytes32[](0) - }); - rollup.propose(args, signatures, body, data.blobInputs); + assertEq(inbox.inProgress(), 3, "Invalid in progress"); + assertEq(inbox.getRoot(2), inboxRoot2, "Invalid inbox root"); + assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); + assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - BlockLog memory blockLog = rollup.getBlock(0); - _submitEpochProof( - rollup, - 1, - blockLog.archive, - data.archive, - blockLog.blockHash, - data.blockHash, - bytes32(uint256(42)), - this.getBlobPublicInputs(data.blobInputs) - ); + // We check that the roots in the outbox have correctly been updated. + bytes32 rootEmpty = vm.load(address(outbox), keccak256(abi.encode(1, 0))); + uint256 minHeightEmpty = + uint256(vm.load(address(outbox), bytes32(uint256(keccak256(abi.encode(1, 0))) + 1))); - vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlockNumber.selector, 1, 2)); - _submitEpochProof( - rollup, - 1, - blockLog.archive, - data.archive, - blockLog.blockHash, - data.blockHash, - bytes32(uint256(42)), - new bytes(112) - ); + assertEq(rootEmpty, bytes32(0), "Invalid root"); + assertNotEq(minHeightEmpty, 0, "Invalid min height"); } function testTimestamp() public setUpFor("mixed_block_1") { @@ -569,77 +242,21 @@ contract RollupTest is DecoderBase { vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); rollup.prune(); - _testBlock("mixed_block_1", false); + _proposeBlock("mixed_block_1", 1); vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NothingToPrune.selector)); rollup.prune(); } - function testPrune() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false); - - assertEq(inbox.inProgress(), 3, "Invalid in progress"); - - // @note Fetch the inbox root of block 2. This should be frozen when block 1 is proposed. - // Even if we end up reverting block 1, we should still see the same root in the inbox. - bytes32 inboxRoot2 = inbox.getRoot(2); - - BlockLog memory blockLog = rollup.getBlock(1); - Slot prunableAt = blockLog.slotNumber + Epoch.wrap(2).toSlots(); - - Timestamp timeOfPrune = rollup.getTimestampForSlot(prunableAt); - vm.warp(Timestamp.unwrap(timeOfPrune)); - - assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - - // @note Get the root and min height that we have in the outbox. - // We read it directly in storage because it is not yet proven, so the getter will give (0, 0). - // The values are stored such that we can check that after pruning, and inserting a new block, - // we will override it. - bytes32 rootMixed = vm.load(address(outbox), keccak256(abi.encode(1, 0))); - uint256 minHeightMixed = - uint256(vm.load(address(outbox), bytes32(uint256(keccak256(abi.encode(1, 0))) + 1))); - - 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"); - assertEq(inbox.getRoot(2), inboxRoot2, "Invalid inbox root"); - assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - - // We check that the roots in the outbox have correctly been updated. - bytes32 rootEmpty = vm.load(address(outbox), keccak256(abi.encode(1, 0))); - uint256 minHeightEmpty = - uint256(vm.load(address(outbox), bytes32(uint256(keccak256(abi.encode(1, 0))) + 1))); - - assertEq(rootEmpty, bytes32(0), "Invalid root"); - assertNotEq(minHeightEmpty, 0, "Invalid min height"); - } - function testShouldNotBeTooEagerToPrune() public setUpFor("mixed_block_1") { warpToL2Slot(1); - _testBlock("mixed_block_1", false, 1); + _proposeBlock("mixed_block_1", 1); // we prove epoch 0 rollup.setAssumeProvenThroughBlockNumber(rollup.getPendingBlockNumber()); // jump to epoch 1 warpToL2Slot(EPOCH_DURATION); - _testBlock("mixed_block_2", false, EPOCH_DURATION); + _proposeBlock("mixed_block_2", EPOCH_DURATION); // jump to epoch 2 warpToL2Slot(EPOCH_DURATION * 2); @@ -649,10 +266,11 @@ contract RollupTest is DecoderBase { } function testPruneDuringPropose() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); + _proposeBlock("mixed_block_1", 1); assertEq(rollup.getEpochToProve(), 0, "Invalid epoch to prove"); - warpToL2Slot(EPOCH_DURATION * 2); - _testBlock("mixed_block_1", false, Epoch.wrap(2).toSlots().unwrap()); + + // the same block is proposed, with the diff in slot number. + _proposeBlock("mixed_block_1", rollup.getProofSubmissionWindow() + 1); assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); @@ -712,21 +330,30 @@ contract RollupTest is DecoderBase { rollup.propose(args, signatures, data.body, data.blobInputs); } + struct TestBlockFeeStruct { + uint256 provingCostPerMana; + uint256 baseFee; + uint256 feeAmount; + uint256 portalBalance; + uint256 manaUsed; + uint256 time; + } + function testBlockFee() public setUpFor("mixed_block_1") { - uint256 feeAmount = Constants.FEE_JUICE_INITIAL_MINT + 0.5e18; + TestBlockFeeStruct memory interim; DecoderBase.Data memory data = load("mixed_block_1").block; - uint256 portalBalance = testERC20.balanceOf(address(feeJuicePortal)); + interim.portalBalance = testERC20.balanceOf(address(feeJuicePortal)); + interim.provingCostPerMana = rollup.getProvingCostPerMana(); + interim.manaUsed = 1e6; // Progress time as necessary vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp)); + interim.time = block.timestamp; + { bytes memory header = data.header; - assembly { - mstore(add(header, add(0x20, 0x0248)), feeAmount) - } - assertEq(testERC20.balanceOf(address(rollup)), 0, "invalid rollup balance"); // We jump to the time of the block. (unless it is in the past) @@ -736,7 +363,12 @@ contract RollupTest is DecoderBase { assertEq(coinbaseBalance, 0, "invalid initial coinbase balance"); skipBlobCheck(address(rollup)); - header = _updateHeaderBaseFee(header); + interim.baseFee = rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true); + header = _updateHeaderBaseFee(header, interim.baseFee); + header = _updateHeaderManaUsed(header, interim.manaUsed); + // We mess up the fees and say that someone is paying a massive priority which surpass the amount available. + interim.feeAmount = interim.manaUsed * interim.baseFee + interim.portalBalance; + header = _updateHeaderTotalFees(header, interim.feeAmount); // Assert that balance have NOT been increased by proposing the block ProposeArgs memory args = ProposeArgs({ @@ -755,13 +387,9 @@ contract RollupTest is DecoderBase { } BlockLog memory blockLog = rollup.getBlock(0); + warpToL2Slot(rollup.getProofSubmissionWindow() - 1); - quote.epochToProve = Epoch.wrap(1); - quote.validUntilSlot = Epoch.wrap(2).toSlots(); - signedQuote = _quoteToSignedQuote(quote); - - warpToL2Slot(EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS() - 1); - rollup.claimEpochProofRight(signedQuote); + address prover = address(0x1234); { bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); @@ -769,21 +397,21 @@ contract RollupTest is DecoderBase { abi.encodeWithSelector( IERC20Errors.ERC20InsufficientBalance.selector, address(feeJuicePortal), - portalBalance, - feeAmount + interim.portalBalance, + interim.feeAmount ) ); _submitEpochProofWithFee( - rollup, + 1, 1, blockLog.archive, data.archive, blockLog.blockHash, data.blockHash, - bytes32(uint256(42)), blobPublicInputs, + prover, data.decodedHeader.globalVariables.coinbase, - feeAmount + interim.feeAmount ); } assertEq( @@ -791,40 +419,60 @@ contract RollupTest is DecoderBase { 0, "invalid coinbase balance" ); - assertEq(testERC20.balanceOf(address(quote.prover)), 0, "invalid prover balance"); + assertEq( + rollup.getSequencerRewards(data.decodedHeader.globalVariables.coinbase), + 0, + "invalid sequencer rewards" + ); + assertEq(testERC20.balanceOf(prover), 0, "invalid prover balance"); + assertEq(rollup.getCollectiveProverRewardsForEpoch(Epoch.wrap(0)), 0, "invalid prover rewards"); { - testERC20.mint(address(feeJuicePortal), feeAmount - portalBalance); + testERC20.mint(address(feeJuicePortal), interim.feeAmount - interim.portalBalance); // When the block is proven we should have received the funds _submitEpochProofWithFee( - rollup, + 1, 1, blockLog.archive, data.archive, blockLog.blockHash, data.blockHash, - bytes32(uint256(42)), this.getBlobPublicInputs(data.blobInputs), + address(42), data.decodedHeader.globalVariables.coinbase, - feeAmount + interim.feeAmount ); - uint256 expectedReward = rewardDistributor.BLOCK_REWARD() + feeAmount; - uint256 expectedProverReward = Math.mulDiv(expectedReward, quote.basisPointFee, 10_000); - uint256 expectedSequencerReward = expectedReward - expectedProverReward; + uint256 provingCosts = Math.mulDiv(interim.provingCostPerMana, rollup.getFeeAssetPrice(), 1e9); + + uint256 expectedProverReward = + rewardDistributor.BLOCK_REWARD() / 2 + provingCosts * interim.manaUsed; + uint256 expectedSequencerReward = + rewardDistributor.BLOCK_REWARD() / 2 + interim.feeAmount - provingCosts * interim.manaUsed; assertEq( - testERC20.balanceOf(data.decodedHeader.globalVariables.coinbase), + rollup.getSequencerRewards(data.decodedHeader.globalVariables.coinbase), expectedSequencerReward, - "invalid coinbase balance" + "invalid sequencer rewards" + ); + + Epoch epoch = rollup.getBlock(1).slotNumber.epochFromSlot(); + + assertEq( + rollup.getCollectiveProverRewardsForEpoch(epoch), + expectedProverReward, + "invalid prover rewards" ); - assertEq(testERC20.balanceOf(quote.prover), expectedProverReward, "invalid prover balance"); } } function testMixedBlock(bool _toProve) public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", _toProve); + _proposeBlock("mixed_block_1", 1); + + if (_toProve) { + _proveBlocks("mixed_block_", 1, 1, address(0)); + } assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), _toProve ? 1 : 0, "Invalid proven block number"); @@ -833,15 +481,23 @@ contract RollupTest is DecoderBase { function testConsecutiveMixedBlocks(uint256 _blocksToProve) public setUpFor("mixed_block_1") { uint256 toProve = bound(_blocksToProve, 0, 2); - _testBlock("mixed_block_1", toProve > 0); - _testBlock("mixed_block_2", toProve > 1); + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", 2); + + if (toProve > 0) { + _proveBlocks("mixed_block_", 1, toProve, address(0)); + } assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), 0 + toProve, "Invalid proven block number"); } function testSingleBlock(bool _toProve) public setUpFor("single_tx_block_1") { - _testBlock("single_tx_block_1", _toProve); + _proposeBlock("single_tx_block_1", 1); + + if (_toProve) { + _proveBlocks("single_tx_block_", 1, 1, address(0)); + } assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), _toProve ? 1 : 0, "Invalid proven block number"); @@ -853,16 +509,20 @@ contract RollupTest is DecoderBase { { uint256 toProve = bound(_blocksToProve, 0, 2); - _testBlock("single_tx_block_1", toProve > 0); - _testBlock("single_tx_block_2", toProve > 1); + _proposeBlock("single_tx_block_1", 1); + _proposeBlock("single_tx_block_2", 2); + + if (toProve > 0) { + _proveBlocks("single_tx_block_", 1, toProve, address(0)); + } assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), 0 + toProve, "Invalid proven block number"); } function testRevertSubmittingProofForBlocksAcrossEpochs() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - _testBlock("mixed_block_2", false, TestConstants.AZTEC_EPOCH_DURATION + 1); + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", TestConstants.AZTEC_EPOCH_DURATION + 1); DecoderBase.Data memory data = load("mixed_block_2").block; @@ -891,12 +551,15 @@ contract RollupTest is DecoderBase { bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); vm.expectRevert( - abi.encodeWithSelector(Errors.Rollup__InvalidEpoch.selector, Epoch.wrap(0), Epoch.wrap(1)) + abi.encodeWithSelector( + Errors.Rollup__StartAndEndNotSameEpoch.selector, Epoch.wrap(0), Epoch.wrap(1) + ) ); rollup.submitEpochRootProof( SubmitEpochRootProofArgs({ - epochSize: 2, + start: 1, + end: 2, args: args, fees: fees, blobPublicInputs: blobPublicInputs, @@ -910,8 +573,8 @@ contract RollupTest is DecoderBase { } function testProveEpochWithTwoMixedBlocks() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false, 1); - _testBlock("mixed_block_2", false, 2); + _proposeBlock("mixed_block_1", 1); + _proposeBlock("mixed_block_2", 2); DecoderBase.Data memory data = load("mixed_block_2").block; @@ -922,14 +585,14 @@ contract RollupTest is DecoderBase { this.getBlobPublicInputs(data.blobInputs) ); _submitEpochProof( - rollup, + 1, 2, blockLog.archive, data.archive, blockLog.blockHash, data.blockHash, - bytes32(0), - blobPublicInputs + blobPublicInputs, + address(0) ); assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); @@ -937,40 +600,16 @@ contract RollupTest is DecoderBase { } function testConsecutiveMixedBlocksNonSequentialProof() public setUpFor("mixed_block_1") { - _testBlock("mixed_block_1", false); + _proposeBlock("mixed_block_1", 200); + _proposeBlock("mixed_block_2", 201); - DecoderBase.Data memory data1 = load("mixed_block_1").block; - DecoderBase.Data memory data2 = load("mixed_block_2").block; - bytes32[] memory txHashes = new bytes32[](0); - - vm.warp(max(block.timestamp, data2.decodedHeader.globalVariables.timestamp)); - skipBlobCheck(address(rollup)); - ProposeArgs memory args = ProposeArgs({ - header: _updateHeaderBaseFee(data2.header), - archive: data2.archive, - blockHash: data2.blockHash, - oracleInput: OracleInput(0, 0), - txHashes: txHashes - }); - rollup.propose(args, signatures, data2.body, data2.blobInputs); - - // Skips proving of block 1 - BlockLog memory blockLog = rollup.getBlock(0); - bytes memory blobPublicInputs = this.getBlobPublicInputs(data1.blobInputs); - vm.expectRevert( - abi.encodeWithSelector( - Errors.Rollup__InvalidPreviousArchive.selector, blockLog.archive, data1.archive - ) - ); - _submitEpochProof( - rollup, - 1, - data1.archive, - data2.archive, - data1.archive, - data2.archive, - bytes32(0), - blobPublicInputs + // Should fail here. + _proveBlocksFail( + "mixed_block_", + 2, + 2, + address(0), + abi.encodeWithSelector(Errors.Rollup__StartIsNotFirstBlockOfEpoch.selector) ); assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); @@ -978,15 +617,24 @@ contract RollupTest is DecoderBase { } function testEmptyBlock(bool _toProve) public setUpFor("empty_block_1") { - _testBlock("empty_block_1", _toProve); + _proposeBlock("empty_block_1", 1); + + if (_toProve) { + _proveBlocks("empty_block_", 1, 1, address(0)); + } + assertEq(rollup.getPendingBlockNumber(), 1, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), _toProve ? 1 : 0, "Invalid proven block number"); } function testConsecutiveEmptyBlocks(uint256 _blocksToProve) public setUpFor("empty_block_1") { uint256 toProve = bound(_blocksToProve, 0, 2); - _testBlock("empty_block_1", toProve > 0); - _testBlock("empty_block_2", toProve > 1); + _proposeBlock("empty_block_1", 1); + _proposeBlock("empty_block_2", 2); + + if (toProve > 0) { + _proveBlocks("empty_block_", 1, toProve, address(0)); + } assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); assertEq(rollup.getProvenBlockNumber(), 0 + toProve, "Invalid proven block number"); @@ -1086,32 +734,8 @@ contract RollupTest is DecoderBase { rollup.propose(args, signatures, body, new bytes(144)); } - function testBlocksWithAssumeProven() public setUpFor("mixed_block_1") { - rollup.setAssumeProvenThroughBlockNumber(1); - assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - - _testBlock("mixed_block_1", false); - _testBlock("mixed_block_2", false); - - assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 1, "Invalid proven block number"); - } - - function testSetAssumeProvenAfterBlocksProcessed() public setUpFor("mixed_block_1") { - assertEq(rollup.getPendingBlockNumber(), 0, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 0, "Invalid proven block number"); - - _testBlock("mixed_block_1", false); - _testBlock("mixed_block_2", false); - rollup.setAssumeProvenThroughBlockNumber(1); - - assertEq(rollup.getPendingBlockNumber(), 2, "Invalid pending block number"); - assertEq(rollup.getProvenBlockNumber(), 1, "Invalid proven block number"); - } - function testSubmitProofNonExistantBlock() public setUpFor("empty_block_1") { - _testBlock("empty_block_1", false); + _proposeBlock("empty_block_1", 1); DecoderBase.Data memory data = load("empty_block_1").block; bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); @@ -1123,14 +747,7 @@ contract RollupTest is DecoderBase { ) ); _submitEpochProof( - rollup, - 1, - wrong, - data.archive, - blockLog.blockHash, - data.blockHash, - bytes32(0), - blobPublicInputs + 1, 1, wrong, data.archive, blockLog.blockHash, data.blockHash, blobPublicInputs, address(0) ); // TODO: Reenable when we setup proper initial block hash @@ -1141,7 +758,7 @@ contract RollupTest is DecoderBase { } function testSubmitProofInvalidArchive() public setUpFor("empty_block_1") { - _testBlock("empty_block_1", false); + _proposeBlock("empty_block_1", 1); DecoderBase.Data memory data = load("empty_block_1").block; bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); @@ -1152,44 +769,44 @@ contract RollupTest is DecoderBase { abi.encodeWithSelector(Errors.Rollup__InvalidArchive.selector, data.archive, 0xdeadbeef) ); _submitEpochProof( - rollup, + 1, 1, blockLog.archive, wrongArchive, blockLog.blockHash, data.blockHash, - bytes32(0), - blobPublicInputs + blobPublicInputs, + address(0) ); } function testSubmitProofInvalidBlockHash() public setUpFor("empty_block_1") { - _testBlock("empty_block_1", false); + _proposeBlock("empty_block_1", 1); DecoderBase.Data memory data = load("empty_block_1").block; bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); bytes32 wrongBlockHash = bytes32(uint256(0xdeadbeef)); - BlockLog memory blockLog = rollup.getBlock(0); + BlockLog memory parentBlockLog = rollup.getBlock(0); vm.expectRevert( abi.encodeWithSelector( Errors.Rollup__InvalidBlockHash.selector, data.blockHash, wrongBlockHash ) ); _submitEpochProof( - rollup, 1, - blockLog.archive, + 1, + parentBlockLog.archive, data.archive, - blockLog.blockHash, + parentBlockLog.blockHash, wrongBlockHash, - bytes32(0), - blobPublicInputs + blobPublicInputs, + address(0) ); } - function testSubmitProofInvalidBlobPublicInput() public setUpFor("empty_block_1") { - _testBlock("empty_block_1", false); + function _testSubmitProofInvalidBlobPublicInput() public setUpFor("empty_block_1") { + _proposeBlock({_name: "empty_block_1", _slotNumber: 0}); DecoderBase.Data memory data = load("empty_block_1").block; bytes memory blobPublicInputs = this.getBlobPublicInputs(data.blobInputs); @@ -1208,196 +825,71 @@ contract RollupTest is DecoderBase { ) ); _submitEpochProof( - rollup, + 1, 1, blockLog.archive, data.archive, blockLog.blockHash, data.blockHash, - bytes32(0), - blobPublicInputs + blobPublicInputs, + address(0) ); } - function _testBlock(string memory name, bool _submitProof) public { - _testBlock(name, _submitProof, 0); - } - - function _updateHeaderBaseFee(bytes memory _header) internal view returns (bytes memory) { - uint256 baseFee = rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true); - assembly { - mstore(add(_header, add(0x20, 0x0228)), baseFee) - } - return _header; - } - - function _testBlock(string memory name, bool _submitProof, uint256 _slotNumber) public { - DecoderBase.Full memory full = load(name); - bytes memory header = full.block.header; - bytes memory blobInputs = full.block.blobInputs; - - Slot slotNumber = Slot.wrap(_slotNumber); - - // Overwrite some timestamps if needed - if (slotNumber != Slot.wrap(0)) { - Timestamp ts = rollup.getTimestampForSlot(slotNumber); - - full.block.decodedHeader.globalVariables.timestamp = Timestamp.unwrap(ts); - full.block.decodedHeader.globalVariables.slotNumber = Slot.unwrap(slotNumber); - assembly { - mstore(add(header, add(0x20, 0x0194)), slotNumber) - mstore(add(header, add(0x20, 0x01b4)), ts) - } - } - - // We jump to the time of the block. (unless it is in the past) - vm.warp(max(block.timestamp, full.block.decodedHeader.globalVariables.timestamp)); - - _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); - - { - bytes32[] memory blobHashes = new bytes32[](1); - // The below is the blob hash == bytes [1:33] of _blobInput - bytes32 blobHash; - assembly { - blobHash := mload(add(blobInputs, 0x21)) - } - blobHashes[0] = blobHash; - vm.blobhashes(blobHashes); - } - header = _updateHeaderBaseFee(header); - - ProposeArgs memory args = ProposeArgs({ - header: header, - archive: full.block.archive, - blockHash: full.block.blockHash, - oracleInput: OracleInput(0, 0), - txHashes: new bytes32[](0) - }); - rollup.propose(args, signatures, full.block.body, blobInputs); - - if (_submitProof) { - uint256 pre = rollup.getProvenBlockNumber(); - BlockLog memory blockLog = rollup.getBlock(pre); - - _submitEpochProof( - rollup, - 1, - blockLog.archive, - args.archive, - blockLog.blockHash, - full.block.blockHash, - bytes32(0), - this.getBlobPublicInputs(blobInputs) - ); - assertEq(pre + 1, rollup.getProvenBlockNumber(), "Block not proven"); - } - - bytes32 l2ToL1MessageTreeRoot; - uint32 numTxs = full.block.numTxs; - if (numTxs != 0) { - // NB: The below works with full blocks because we require the largest possible subtrees - // for L2 to L1 messages - usually we make variable height subtrees, the roots of which - // form a balanced tree - - // The below is a little janky - we know that this test deals with full txs with equal numbers - // of msgs or txs with no messages, so the division works - // TODO edit full.messages to include information about msgs per tx? - uint256 subTreeHeight = full.messages.l2ToL1Messages.length == 0 - ? 0 - : merkleTestUtil.calculateTreeHeightFromSize(full.messages.l2ToL1Messages.length / numTxs); - uint256 outHashTreeHeight = - numTxs == 1 ? 0 : merkleTestUtil.calculateTreeHeightFromSize(numTxs); - uint256 numMessagesWithPadding = numTxs * Constants.MAX_L2_TO_L1_MSGS_PER_TX; - - uint256 treeHeight = subTreeHeight + outHashTreeHeight; - NaiveMerkle tree = new NaiveMerkle(treeHeight); - for (uint256 i = 0; i < numMessagesWithPadding; i++) { - if (i < full.messages.l2ToL1Messages.length) { - tree.insertLeaf(full.messages.l2ToL1Messages[i]); - } else { - tree.insertLeaf(bytes32(0)); - } - } - - l2ToL1MessageTreeRoot = tree.computeRoot(); - } - - (bytes32 root,) = outbox.getRootData(full.block.decodedHeader.globalVariables.blockNumber); - - // If we are trying to read a block beyond the proven chain, we should see "nothing". - if (rollup.getProvenBlockNumber() >= full.block.decodedHeader.globalVariables.blockNumber) { - assertEq(l2ToL1MessageTreeRoot, root, "Invalid l2 to l1 message tree root"); - } else { - assertEq(root, bytes32(0), "Invalid outbox root"); - } - - assertEq(rollup.archive(), args.archive, "Invalid archive"); - } - - function _populateInbox(address _sender, bytes32 _recipient, bytes32[] memory _contents) internal { - for (uint256 i = 0; i < _contents.length; i++) { - vm.prank(_sender); - inbox.sendL2Message( - DataStructures.L2Actor({actor: _recipient, version: 1}), _contents[i], bytes32(0) - ); - } - } - function _submitEpochProof( - Rollup _rollup, - uint256 _epochSize, - bytes32 _previousArchive, - bytes32 _endArchive, - bytes32 _previousBlockHash, - bytes32 _endBlockHash, - bytes32 _proverId, - bytes memory _blobPublicInputs + uint256 _start, + uint256 _end, + bytes32 _prevArchive, + bytes32 _archive, + bytes32 _prevBlockHash, + bytes32 _blockHash, + bytes memory _blobPublicInputs, + address _prover ) internal { _submitEpochProofWithFee( - _rollup, - _epochSize, - _previousArchive, - _endArchive, - _previousBlockHash, - _endBlockHash, - _proverId, + _start, + _end, + _prevArchive, + _archive, + _prevBlockHash, + _blockHash, _blobPublicInputs, + _prover, address(0), - uint256(0) + 0 ); } function _submitEpochProofWithFee( - Rollup _rollup, - uint256 _epochSize, - bytes32 _previousArchive, - bytes32 _endArchive, - bytes32 _previousBlockHash, - bytes32 _endBlockHash, - bytes32 _proverId, + uint256 _start, + uint256 _end, + bytes32 _prevArchive, + bytes32 _archive, + bytes32 _prevBlockHash, + bytes32 _blockHash, bytes memory _blobPublicInputs, - address _feeRecipient, - uint256 _feeAmount + address _prover, + address _coinbase, + uint256 _fee ) internal { bytes32[7] memory args = [ - _previousArchive, - _endArchive, - _previousBlockHash, - _endBlockHash, - bytes32(0), - bytes32(0), - _proverId + _prevArchive, + _archive, + _prevBlockHash, + _blockHash, + bytes32(0), // WHAT ? + bytes32(0), // WHAT ? + bytes32(uint256(uint160(bytes20(_prover)))) // Need the address to be left padded within the bytes32 ]; bytes32[] memory fees = new bytes32[](Constants.AZTEC_MAX_EPOCH_DURATION * 2); + fees[0] = bytes32(uint256(uint160(bytes20(_coinbase)))); // Need the address to be left padded within the bytes32 + fees[1] = bytes32(_fee); - fees[0] = bytes32(uint256(uint160(_feeRecipient))); - fees[1] = bytes32(_feeAmount); - - _rollup.submitEpochRootProof( + rollup.submitEpochRootProof( SubmitEpochRootProofArgs({ - epochSize: _epochSize, + start: _start, + end: _end, args: args, fees: fees, blobPublicInputs: _blobPublicInputs, @@ -1406,17 +898,4 @@ contract RollupTest is DecoderBase { }) ); } - - function _quoteToSignedQuote(EpochProofQuote memory _quote) - internal - view - returns (SignedEpochProofQuote memory) - { - bytes32 digest = rollup.quoteToDigest(_quote); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); - return SignedEpochProofQuote({ - quote: _quote, - signature: Signature({isEmpty: false, v: v, r: r, s: s}) - }); - } } diff --git a/l1-contracts/test/decoders/Base.sol b/l1-contracts/test/base/DecoderBase.sol similarity index 100% rename from l1-contracts/test/decoders/Base.sol rename to l1-contracts/test/base/DecoderBase.sol diff --git a/l1-contracts/test/base/RollupBase.sol b/l1-contracts/test/base/RollupBase.sol new file mode 100644 index 000000000000..b7f5f278cb97 --- /dev/null +++ b/l1-contracts/test/base/RollupBase.sol @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {DecoderBase} from "./DecoderBase.sol"; + +import {IInstance} from "@aztec/core/interfaces/IInstance.sol"; +import {BlockLog, SubmitEpochRootProofArgs} from "@aztec/core/interfaces/IRollup.sol"; +import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; +import {Strings} from "@oz/utils/Strings.sol"; +import {NaiveMerkle} from "../merkle/Naive.sol"; +import {MerkleTestUtil} from "../merkle/TestUtil.sol"; +import { + Timestamp, Slot, Epoch, SlotLib, EpochLib, TimeLib +} from "@aztec/core/libraries/TimeLib.sol"; +import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; +import { + ProposeArgs, OracleInput, ProposeLib +} from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; +import {Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; +import {Inbox} from "@aztec/core/messagebridge/Inbox.sol"; +import {Outbox} from "@aztec/core/messagebridge/Outbox.sol"; + +contract RollupBase is DecoderBase { + IInstance internal rollup; + Inbox internal inbox; + Outbox internal outbox; + MerkleTestUtil internal merkleTestUtil = new MerkleTestUtil(); + + Signature[] internal signatures; + + mapping(uint256 => uint256) internal blockFees; + + function _proveBlocks(string memory _name, uint256 _start, uint256 _end, address _prover) + internal + { + _proveBlocks(_name, _start, _end, _prover, ""); + } + + function _proveBlocksFail( + string memory _name, + uint256 _start, + uint256 _end, + address _prover, + bytes memory _revertMsg + ) internal { + _proveBlocks(_name, _start, _end, _prover, _revertMsg); + } + + function _proveBlocks( + string memory _name, + uint256 _start, + uint256 _end, + address _prover, + bytes memory _revertMsg + ) private { + DecoderBase.Full memory startFull = load(string.concat(_name, Strings.toString(_start))); + DecoderBase.Full memory endFull = load(string.concat(_name, Strings.toString(_end))); + + uint256 startBlockNumber = uint256(startFull.block.decodedHeader.globalVariables.blockNumber); + uint256 endBlockNumber = uint256(endFull.block.decodedHeader.globalVariables.blockNumber); + + assertEq(startBlockNumber, _start, "Invalid start block number"); + assertEq(endBlockNumber, _end, "Invalid end block number"); + + BlockLog memory parentBlockLog = rollup.getBlock(startBlockNumber - 1); + + // What are these even? + bytes32[7] memory args = [ + parentBlockLog.archive, + endFull.block.archive, + parentBlockLog.blockHash, + endFull.block.blockHash, + bytes32(0), // WHAT ? + bytes32(0), // WHAT ? + bytes32(uint256(uint160(bytes20(_prover)))) // Need the address to be left padded within the bytes32 + ]; + + bytes32[] memory fees = new bytes32[](Constants.AZTEC_MAX_EPOCH_DURATION * 2); + bytes memory blobPublicInputs; + + uint256 size = endBlockNumber - startBlockNumber + 1; + for (uint256 i = 0; i < size; i++) { + fees[i * 2] = bytes32(uint256(uint160(bytes20(("sequencer"))))); // Need the address to be left padded within the bytes32 + fees[i * 2 + 1] = bytes32(uint256(blockFees[startBlockNumber + i])); + + string memory blockName = string.concat(_name, Strings.toString(startBlockNumber + i)); + DecoderBase.Full memory blockFull = load(blockName); + blobPublicInputs = + abi.encodePacked(blobPublicInputs, this.getBlobPublicInputs(blockFull.block.blobInputs)); + } + + // All the way down here if reverting. + + if (_revertMsg.length > 0) { + vm.expectRevert(_revertMsg); + } + + rollup.submitEpochRootProof( + SubmitEpochRootProofArgs({ + start: startBlockNumber, + end: endBlockNumber, + args: args, + fees: fees, + blobPublicInputs: blobPublicInputs, + aggregationObject: "", + proof: "" + }) + ); + } + + function _updateHeaderBaseFee(bytes memory _header, uint256 _baseFee) + internal + pure + returns (bytes memory) + { + assembly { + mstore(add(_header, add(0x20, 0x0228)), _baseFee) + } + return _header; + } + + function _updateHeaderManaUsed(bytes memory _header, uint256 _manaUsed) + internal + pure + returns (bytes memory) + { + assembly { + mstore(add(_header, add(0x20, 0x0268)), _manaUsed) + } + return _header; + } + + function _updateHeaderTotalFees(bytes memory _header, uint256 _totalFees) + internal + pure + returns (bytes memory) + { + assembly { + mstore(add(_header, add(0x20, 0x0248)), _totalFees) + } + return _header; + } + + function _proposeBlock(string memory _name, uint256 _slotNumber) public { + _proposeBlock(_name, _slotNumber, 0); + } + + function _proposeBlock(string memory _name, uint256 _slotNumber, uint256 _manaUsed) public { + DecoderBase.Full memory full = load(_name); + bytes memory header = full.block.header; + bytes memory blobInputs = full.block.blobInputs; + + Slot slotNumber = Slot.wrap(_slotNumber); + + // Overwrite some timestamps if needed + if (slotNumber != Slot.wrap(0)) { + Timestamp ts = rollup.getTimestampForSlot(slotNumber); + + full.block.decodedHeader.globalVariables.timestamp = Timestamp.unwrap(ts); + full.block.decodedHeader.globalVariables.slotNumber = Slot.unwrap(slotNumber); + assembly { + mstore(add(header, add(0x20, 0x0194)), slotNumber) + mstore(add(header, add(0x20, 0x01b4)), ts) + } + } + + uint256 baseFee = rollup.getManaBaseFeeAt( + Timestamp.wrap(full.block.decodedHeader.globalVariables.timestamp), true + ); + header = _updateHeaderBaseFee(header, baseFee); + header = _updateHeaderManaUsed(header, _manaUsed); + header = _updateHeaderTotalFees(header, _manaUsed * baseFee); + + blockFees[full.block.decodedHeader.globalVariables.blockNumber] = _manaUsed * baseFee; + + // We jump to the time of the block. (unless it is in the past) + vm.warp(max(block.timestamp, full.block.decodedHeader.globalVariables.timestamp)); + + _populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content); + + { + bytes32[] memory blobHashes = new bytes32[](1); + // The below is the blob hash == bytes [1:33] of _blobInput + bytes32 blobHash; + assembly { + blobHash := mload(add(blobInputs, 0x21)) + } + blobHashes[0] = blobHash; + vm.blobhashes(blobHashes); + } + + ProposeArgs memory args = ProposeArgs({ + header: header, + archive: full.block.archive, + blockHash: full.block.blockHash, + oracleInput: OracleInput(0, 0), + txHashes: new bytes32[](0) + }); + rollup.propose(args, signatures, full.block.body, blobInputs); + + bytes32 l2ToL1MessageTreeRoot; + uint32 numTxs = full.block.numTxs; + if (numTxs != 0) { + // NB: The below works with full blocks because we require the largest possible subtrees + // for L2 to L1 messages - usually we make variable height subtrees, the roots of which + // form a balanced tree + + // The below is a little janky - we know that this test deals with full txs with equal numbers + // of msgs or txs with no messages, so the division works + // TODO edit full.messages to include information about msgs per tx? + uint256 subTreeHeight = full.messages.l2ToL1Messages.length == 0 + ? 0 + : merkleTestUtil.calculateTreeHeightFromSize(full.messages.l2ToL1Messages.length / numTxs); + uint256 outHashTreeHeight = + numTxs == 1 ? 0 : merkleTestUtil.calculateTreeHeightFromSize(numTxs); + uint256 numMessagesWithPadding = numTxs * Constants.MAX_L2_TO_L1_MSGS_PER_TX; + + uint256 treeHeight = subTreeHeight + outHashTreeHeight; + NaiveMerkle tree = new NaiveMerkle(treeHeight); + for (uint256 i = 0; i < numMessagesWithPadding; i++) { + if (i < full.messages.l2ToL1Messages.length) { + tree.insertLeaf(full.messages.l2ToL1Messages[i]); + } else { + tree.insertLeaf(bytes32(0)); + } + } + + l2ToL1MessageTreeRoot = tree.computeRoot(); + } + + outbox = Outbox(address(rollup.OUTBOX())); + (bytes32 root,) = outbox.getRootData(full.block.decodedHeader.globalVariables.blockNumber); + + // If we are trying to read a block beyond the proven chain, we should see "nothing". + if (rollup.getProvenBlockNumber() >= full.block.decodedHeader.globalVariables.blockNumber) { + assertEq(l2ToL1MessageTreeRoot, root, "Invalid l2 to l1 message tree root"); + } else { + assertEq(root, bytes32(0), "Invalid outbox root"); + } + + assertEq(rollup.archive(), args.archive, "Invalid archive"); + } + + function _populateInbox(address _sender, bytes32 _recipient, bytes32[] memory _contents) internal { + inbox = Inbox(address(rollup.INBOX())); + + for (uint256 i = 0; i < _contents.length; i++) { + vm.prank(_sender); + inbox.sendL2Message( + DataStructures.L2Actor({actor: _recipient, version: 1}), _contents[i], bytes32(0) + ); + } + } + + function getBlobPublicInputs(bytes calldata _blobsInput) + public + pure + returns (bytes memory blobPublicInputs) + { + uint8 numBlobs = uint8(_blobsInput[0]); + blobPublicInputs = abi.encodePacked(numBlobs, blobPublicInputs); + for (uint256 i = 0; i < numBlobs; i++) { + // Add 1 for the numBlobs prefix + uint256 blobInputStart = i * 192 + 1; + // We want to extract the bytes we use for public inputs: + // * input[32:64] - z + // * input[64:96] - y + // * input[96:144] - commitment C + // Out of 192 bytes per blob. + blobPublicInputs = + abi.encodePacked(blobPublicInputs, _blobsInput[blobInputStart + 32:blobInputStart + 144]); + } + } + + function getBlobPublicInputsHash(bytes calldata _blobPublicInputs) + public + pure + returns (bytes32 publicInputsHash) + { + uint8 numBlobs = uint8(_blobPublicInputs[0]); + publicInputsHash = sha256(abi.encodePacked(_blobPublicInputs[1:1 + numBlobs * 112])); + } +} diff --git a/l1-contracts/test/decoders/Decoders.t.sol b/l1-contracts/test/decoders/Decoders.t.sol index 6a276be885a0..c0568d33612b 100644 --- a/l1-contracts/test/decoders/Decoders.t.sol +++ b/l1-contracts/test/decoders/Decoders.t.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {DecoderBase} from "./Base.sol"; +import {DecoderBase} from "../base/DecoderBase.sol"; import {HeaderLibHelper} from "./helpers/HeaderLibHelper.sol"; import {Header} from "@aztec/core/libraries/RollupLibs/HeaderLib.sol"; diff --git a/l1-contracts/test/fees/FeeModelTestPoints.t.sol b/l1-contracts/test/fees/FeeModelTestPoints.t.sol index 368df77e6029..31c8f3b90aae 100644 --- a/l1-contracts/test/fees/FeeModelTestPoints.t.sol +++ b/l1-contracts/test/fees/FeeModelTestPoints.t.sol @@ -5,6 +5,12 @@ pragma solidity >=0.8.27; import {TestBase} from "../base/Base.sol"; import {OracleInput as FeeMathOracleInput} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import { + MAX_PROVING_COST_MODIFIER, + MAX_FEE_ASSET_PRICE_MODIFIER, + MINIMUM_CONGESTION_MULTIPLIER +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {Math} from "@oz/utils/math/Math.sol"; // Remember that foundry json parsing is alphabetically done, so you MUST // sort the struct fields alphabetically or prepare for a headache. @@ -134,11 +140,48 @@ contract FeeModelTestPoints is TestBase { ); } - function assertEq(ManaBaseFeeComponents memory a, ManaBaseFeeComponents memory b) internal pure { - assertEq(a.congestion_cost, b.congestion_cost, "congestion_cost mismatch"); - assertEq(a.congestion_multiplier, b.congestion_multiplier, "congestion_multiplier mismatch"); - assertEq(a.data_cost, b.data_cost, "data_cost mismatch"); - assertEq(a.gas_cost, b.gas_cost, "gas_cost mismatch"); - assertEq(a.proving_cost, b.proving_cost, "proving_cost mismatch"); + function assertEq( + ManaBaseFeeComponents memory a, + ManaBaseFeeComponents memory b, + string memory _message + ) internal pure { + assertEq( + a.congestion_cost, b.congestion_cost, string.concat(_message, " congestion_cost mismatch") + ); + assertEq( + a.congestion_multiplier, + b.congestion_multiplier, + string.concat(_message, " congestion_multiplier mismatch") + ); + assertEq(a.data_cost, b.data_cost, string.concat(_message, " data_cost mismatch")); + assertEq(a.gas_cost, b.gas_cost, string.concat(_message, " gas_cost mismatch")); + assertEq(a.proving_cost, b.proving_cost, string.concat(_message, " proving_cost mismatch")); + } + + function manipulateProvingCost(TestPoint memory point) internal pure returns (TestPoint memory) { + point.outputs.mana_base_fee_components_in_wei.proving_cost = 100; + point.outputs.mana_base_fee_components_in_fee_asset.proving_cost = Math.mulDiv( + point.outputs.mana_base_fee_components_in_wei.proving_cost, + point.outputs.fee_asset_price_at_execution, + 1e9, + Math.Rounding.Ceil + ); + + uint256 total = point.outputs.mana_base_fee_components_in_wei.data_cost + + point.outputs.mana_base_fee_components_in_wei.gas_cost + + point.outputs.mana_base_fee_components_in_wei.proving_cost; + + point.outputs.mana_base_fee_components_in_wei.congestion_cost = ( + total * point.outputs.mana_base_fee_components_in_wei.congestion_multiplier + / MINIMUM_CONGESTION_MULTIPLIER - total + ); + point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost = Math.mulDiv( + point.outputs.mana_base_fee_components_in_wei.congestion_cost, + point.outputs.fee_asset_price_at_execution, + 1e9, + Math.Rounding.Ceil + ); + + return point; } } diff --git a/l1-contracts/test/fees/FeeRollup.t.sol b/l1-contracts/test/fees/FeeRollup.t.sol index 8093c87f2617..2cb0600e22d6 100644 --- a/l1-contracts/test/fees/FeeRollup.t.sol +++ b/l1-contracts/test/fees/FeeRollup.t.sol @@ -2,12 +2,11 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {DecoderBase} from "../decoders/Base.sol"; +import {DecoderBase} from "../base/DecoderBase.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; import {SignatureLib, Signature} from "@aztec/core/libraries/crypto/SignatureLib.sol"; -import {EpochProofQuoteLib} from "@aztec/core/libraries/RollupLibs/EpochProofQuoteLib.sol"; import {Math} from "@oz/utils/math/Math.sol"; import {Registry} from "@aztec/governance/Registry.sol"; @@ -24,7 +23,6 @@ import { SubmitEpochRootProofArgs } from "@aztec/core/Rollup.sol"; import {IRollup} from "@aztec/core/interfaces/IRollup.sol"; -import {IProofCommitmentEscrow} from "@aztec/core/interfaces/IProofCommitmentEscrow.sol"; import {FeeJuicePortal} from "@aztec/core/FeeJuicePortal.sol"; import {NaiveMerkle} from "../merkle/Naive.sol"; import {MerkleTestUtil} from "../merkle/TestUtil.sol"; @@ -33,12 +31,16 @@ import {TestConstants} from "../harnesses/TestConstants.sol"; import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol"; import {IERC20Errors} from "@oz/interfaces/draft-IERC6093.sol"; import {IFeeJuicePortal} from "@aztec/core/interfaces/IFeeJuicePortal.sol"; -import {IRewardDistributor} from "@aztec/governance/interfaces/IRewardDistributor.sol"; +import {IRewardDistributor, IRegistry} from "@aztec/governance/interfaces/IRewardDistributor.sol"; import { ProposeArgs, OracleInput, ProposeLib } from "@aztec/core/libraries/RollupLibs/ProposeLib.sol"; import {IERC20} from "@oz/token/ERC20/IERC20.sol"; -import {FeeMath, MANA_TARGET} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import { + FeeMath, + MANA_TARGET, + MINIMUM_CONGESTION_MULTIPLIER +} from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; import { FeeHeader as FeeHeaderModel, @@ -51,7 +53,7 @@ import {FeeModelTestPoints, TestPoint} from "./FeeModelTestPoints.t.sol"; import {MinimalFeeModel} from "./MinimalFeeModel.sol"; // solhint-disable comprehensive-interface -contract FakeCanonical { +contract FakeCanonical is IRewardDistributor { uint256 public constant BLOCK_REWARD = 50e18; IERC20 public immutable UNDERLYING; @@ -70,9 +72,16 @@ contract FakeCanonical { return BLOCK_REWARD; } + function claimBlockRewards(address _recipient, uint256 _blocks) external returns (uint256) { + TestERC20(address(UNDERLYING)).mint(_recipient, _blocks * BLOCK_REWARD); + return _blocks * BLOCK_REWARD; + } + function distributeFees(address _recipient, uint256 _amount) external { TestERC20(address(UNDERLYING)).mint(_recipient, _amount); } + + function updateRegistry(IRegistry _registry) external {} } contract FeeRollupTest is FeeModelTestPoints, DecoderBase { @@ -127,7 +136,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { aztecSlotDuration: SLOT_DURATION, aztecEpochDuration: EPOCH_DURATION, targetCommitteeSize: 48, - aztecEpochProofClaimWindowInL2Slots: 16, + aztecProofSubmissionWindow: EPOCH_DURATION * 2, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE @@ -156,7 +165,6 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { // We will be using the genesis for both before and after. This will be impossible // to prove, but we don't need to prove anything here. bytes32 archiveRoot = bytes32(Constants.GENESIS_ARCHIVE_ROOT); - bytes32 blockHash = bytes32(Constants.GENESIS_BLOCK_HASH); bytes32[] memory txHashes = new bytes32[](0); Signature[] memory signatures = new Signature[](0); @@ -170,18 +178,33 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { Timestamp ts = rollup.getTimestampForSlot(slotNumber); uint256 bn = rollup.getPendingBlockNumber() + 1; - uint256 manaBaseFee = ( - point.outputs.mana_base_fee_components_in_fee_asset.data_cost - + point.outputs.mana_base_fee_components_in_fee_asset.gas_cost - + point.outputs.mana_base_fee_components_in_fee_asset.proving_cost - + point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost - ); - - assertEq( - manaBaseFee, - rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true), - "mana base fee mismatch" - ); + uint256 manaBaseFee; + { + uint256 total = point.outputs.mana_base_fee_components_in_wei.data_cost + + point.outputs.mana_base_fee_components_in_wei.gas_cost + 100; + + uint256 congestionCost = Math.mulDiv( + total, + point.outputs.mana_base_fee_components_in_wei.congestion_multiplier, + MINIMUM_CONGESTION_MULTIPLIER, + Math.Rounding.Floor + ) - total; + + uint256 price = point.outputs.fee_asset_price_at_execution; + manaBaseFee = Math.mulDiv( + point.outputs.mana_base_fee_components_in_wei.data_cost, price, 1e9, Math.Rounding.Ceil + ) + + Math.mulDiv( + point.outputs.mana_base_fee_components_in_wei.gas_cost, price, 1e9, Math.Rounding.Ceil + ) + Math.mulDiv(100, price, 1e9, Math.Rounding.Ceil) + + Math.mulDiv(congestionCost, price, 1e9, Math.Rounding.Ceil); + + assertEq( + manaBaseFee, + rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true), + "mana base fee mismatch" + ); + } uint256 manaSpent = point.block_header.mana_spent; @@ -214,7 +237,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { return Block({ archive: archiveRoot, - blockHash: blockHash, + blockHash: bytes32(Constants.GENESIS_BLOCK_HASH), header: header, body: body, blobInputs: full.block.blobInputs, @@ -317,6 +340,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { // will be accepted as a proposal so very useful for testing a long range of blocks. if (rollup.getCurrentSlot() == nextSlot) { TestPoint memory point = points[nextSlot.unwrap() - 1]; + point = manipulateProvingCost(point); L1FeeData memory fees = rollup.getL1FeesAt(Timestamp.wrap(block.timestamp)); uint256 feeAssetPrice = rollup.getFeeAssetPrice(); @@ -372,8 +396,10 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { assertEq(point.outputs.l1_fee_oracle_output.base_fee, fees.baseFee, "base fee mismatch"); assertEq(point.outputs.l1_fee_oracle_output.blob_fee, fees.blobFee, "blob fee mismatch"); - assertEq(point.outputs.mana_base_fee_components_in_wei, components); - assertEq(point.outputs.mana_base_fee_components_in_fee_asset, componentsFeeAsset); + assertEq(point.outputs.mana_base_fee_components_in_wei, components, "in_wei"); + assertEq( + point.outputs.mana_base_fee_components_in_fee_asset, componentsFeeAsset, "in_fee_asset" + ); assertEq(point.parent_fee_header, parentBlockLog.feeHeader); @@ -394,12 +420,14 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { epochSize++; } - uint256 feeSum = 0; + uint256 proverFees = 0; + uint256 sequencerFees = 0; uint256 burnSum = 0; bytes32[] memory fees = new bytes32[](Constants.AZTEC_MAX_EPOCH_DURATION * 2); for (uint256 feeIndex = 0; feeIndex < epochSize; feeIndex++) { TestPoint memory point = points[start + feeIndex - 1]; + point = manipulateProvingCost(point); // We assume that everyone PERFECTLY pays their fees with 0 priority fees and no // overpaying on teardown. @@ -408,20 +436,28 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { + point.outputs.mana_base_fee_components_in_fee_asset.proving_cost + point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost; - uint256 fee = rollup.getBlock(start + feeIndex).feeHeader.manaUsed * baseFee; - feeSum += fee; - burnSum += rollup.getBlock(start + feeIndex).feeHeader.manaUsed - * point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost; + uint256 manaUsed = rollup.getBlock(start + feeIndex).feeHeader.manaUsed; + uint256 fee = manaUsed * baseFee; + + proverFees += + (manaUsed * point.outputs.mana_base_fee_components_in_fee_asset.proving_cost); + + sequencerFees += ( + manaUsed + * ( + point.outputs.mana_base_fee_components_in_fee_asset.data_cost + + point.outputs.mana_base_fee_components_in_fee_asset.gas_cost + ) + ); - fees[feeIndex * 2] = bytes32(uint256(uint160(coinbase))); + burnSum += manaUsed * point.outputs.mana_base_fee_components_in_fee_asset.congestion_cost; + + fees[feeIndex * 2] = bytes32(uint256(uint160(bytes20(coinbase)))); fees[feeIndex * 2 + 1] = bytes32(fee); } - bytes memory aggregationObject = ""; - bytes memory proof = ""; - uint256 cuauhxicalliBalanceBefore = asset.balanceOf(rollup.CUAUHXICALLI()); - uint256 coinbaseBalanceBefore = asset.balanceOf(coinbase); + uint256 sequencerRewardsBefore = rollup.getSequencerRewards(coinbase); bytes32[7] memory args = [ rollup.getBlock(start).archive, @@ -441,25 +477,39 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { abi.encodePacked(blobPublicInputs, this.getBlobPublicInputs(full.block.blobInputs)); } - rollup.submitEpochRootProof( - SubmitEpochRootProofArgs({ - epochSize: epochSize, - args: args, - fees: fees, - blobPublicInputs: blobPublicInputs, - aggregationObject: aggregationObject, - proof: proof - }) - ); + { + rollup.submitEpochRootProof( + SubmitEpochRootProofArgs({ + start: start, + end: start + epochSize - 1, + args: args, + fees: fees, + blobPublicInputs: blobPublicInputs, + aggregationObject: "", + proof: "" + }) + ); + } uint256 burned = asset.balanceOf(rollup.CUAUHXICALLI()) - cuauhxicalliBalanceBefore; - assertEq( - asset.balanceOf(coinbase) - coinbaseBalanceBefore - - fakeCanonical.BLOCK_REWARD() * epochSize + burned, - feeSum, - "Sum of fees does not match" - ); assertEq(burnSum, burned, "Sum of burned does not match"); + + // The reward is not yet distributed, but only accumulated. + { + uint256 newFees = fakeCanonical.BLOCK_REWARD() * epochSize / 2 + sequencerFees; + assertEq( + rollup.getSequencerRewards(coinbase), + sequencerRewardsBefore + newFees, + "sequencer rewards" + ); + } + { + assertEq( + rollup.getCollectiveProverRewardsForEpoch(rollup.getEpochForBlock(start)), + fakeCanonical.BLOCK_REWARD() * epochSize / 2 + proverFees, + "prover rewards" + ); + } } } } @@ -474,10 +524,11 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { assertEq(a, bModel); } - function assertEq(ManaBaseFeeComponentsModel memory a, ManaBaseFeeComponents memory b) - internal - pure - { + function assertEq( + ManaBaseFeeComponentsModel memory a, + ManaBaseFeeComponents memory b, + string memory _message + ) internal pure { ManaBaseFeeComponentsModel memory bModel = ManaBaseFeeComponentsModel({ congestion_cost: b.congestionCost, congestion_multiplier: b.congestionMultiplier, @@ -485,7 +536,7 @@ contract FeeRollupTest is FeeModelTestPoints, DecoderBase { gas_cost: b.gasCost, proving_cost: b.provingCost }); - assertEq(a, bModel); + assertEq(a, bModel, _message); } // This is duplicated from Rollup.t.sol because we need to call it as this.getBlobPublicInputs diff --git a/l1-contracts/test/fees/MinimalFeeModel.sol b/l1-contracts/test/fees/MinimalFeeModel.sol index 8faa096ac261..4166ecd68921 100644 --- a/l1-contracts/test/fees/MinimalFeeModel.sol +++ b/l1-contracts/test/fees/MinimalFeeModel.sol @@ -151,7 +151,7 @@ contract MinimalFeeModel { } function getProvingCost() public view returns (uint256) { - return FeeMath.provingCostPerMana(feeHeaders[populatedThrough].proving_cost_per_mana_numerator); + return 100; } function getCurrentL1Fees() public view returns (L1Fees memory) { diff --git a/l1-contracts/test/fees/MinimalFeeModel.t.sol b/l1-contracts/test/fees/MinimalFeeModel.t.sol index e2c3e3190e88..c892bb188de2 100644 --- a/l1-contracts/test/fees/MinimalFeeModel.t.sol +++ b/l1-contracts/test/fees/MinimalFeeModel.t.sol @@ -14,10 +14,13 @@ import {Errors} from "@aztec/core/libraries/Errors.sol"; import {SlotLib, Slot} from "@aztec/core/libraries/TimeLib.sol"; import { MAX_PROVING_COST_MODIFIER, - MAX_FEE_ASSET_PRICE_MODIFIER + MAX_FEE_ASSET_PRICE_MODIFIER, + MINIMUM_CONGESTION_MULTIPLIER } from "@aztec/core/libraries/RollupLibs/FeeMath.sol"; +import {Math} from "@oz/utils/math/Math.sol"; contract MinimalFeeModelTest is FeeModelTestPoints { + using Math for uint256; using SlotLib for Slot; uint256 internal constant SLOT_DURATION = 36; @@ -48,11 +51,7 @@ contract MinimalFeeModelTest is FeeModelTestPoints { // Then check that we get the same proving costs as the python model for (uint256 i = 0; i < points.length; i++) { - assertEq( - model.getProvingCost(), - points[i].outputs.mana_base_fee_components_in_wei.proving_cost, - "Computed proving cost does not match expected value" - ); + assertEq(model.getProvingCost(), 100, "Computed proving cost does not match expected value"); model.addSlot( OracleInput({ provingCostModifier: points[i].oracle_input.proving_cost_modifier, @@ -165,10 +164,11 @@ contract MinimalFeeModelTest is FeeModelTestPoints { // The fee header is the state that we are storing, so it is the value written at the block submission. FeeHeader memory feeHeader = model.getFeeHeader(point.block_header.slot_number); + point = manipulateProvingCost(point); + // Ensure that we can reproduce the main parts of our test points. // For now, most of the block header is not actually stored in the fee model // but just needed to influence the other values and used for L1 state. - assertEq(point.block_header.block_number, nextSlot, "invalid l2 block number"); assertEq(point.block_header.l1_block_number, block.number, "invalid l1 block number"); assertEq(point.block_header.slot_number, nextSlot, "invalid l2 slot number"); @@ -181,8 +181,10 @@ contract MinimalFeeModelTest is FeeModelTestPoints { ); assertEq(point.outputs.l1_fee_oracle_output, fees, "l1 fee oracle output"); assertEq(point.outputs.l1_gas_oracle_values, model.getL1GasOracleValues()); - assertEq(point.outputs.mana_base_fee_components_in_wei, components); - assertEq(point.outputs.mana_base_fee_components_in_fee_asset, componentsFeeAsset); + assertEq(point.outputs.mana_base_fee_components_in_wei, components, "in_wei"); + assertEq( + point.outputs.mana_base_fee_components_in_fee_asset, componentsFeeAsset, "in_fee_asset" + ); assertEq(point.parent_fee_header, parentFeeHeader); diff --git a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol index 364cd1083c32..c1cd63006648 100644 --- a/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol +++ b/l1-contracts/test/governance/scenario/slashing/Slashing.t.sol @@ -64,7 +64,7 @@ contract SlashingScenario is TestBase { aztecSlotDuration: TestConstants.AZTEC_SLOT_DURATION, aztecEpochDuration: TestConstants.AZTEC_EPOCH_DURATION, targetCommitteeSize: TestConstants.AZTEC_TARGET_COMMITTEE_SIZE, - aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, + aztecProofSubmissionWindow: TestConstants.AZTEC_PROOF_SUBMISSION_WINDOW, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE diff --git a/l1-contracts/test/harnesses/Rollup.sol b/l1-contracts/test/harnesses/Rollup.sol index 27d55a9913e3..2646a0cff539 100644 --- a/l1-contracts/test/harnesses/Rollup.sol +++ b/l1-contracts/test/harnesses/Rollup.sol @@ -28,7 +28,7 @@ contract Rollup is RealRollup { aztecSlotDuration: TestConstants.AZTEC_SLOT_DURATION, aztecEpochDuration: TestConstants.AZTEC_EPOCH_DURATION, targetCommitteeSize: TestConstants.AZTEC_TARGET_COMMITTEE_SIZE, - aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, + aztecProofSubmissionWindow: TestConstants.AZTEC_PROOF_SUBMISSION_WINDOW, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE diff --git a/l1-contracts/test/harnesses/TestConstants.sol b/l1-contracts/test/harnesses/TestConstants.sol index aad8edd6db0d..4e7a0ca4f21c 100644 --- a/l1-contracts/test/harnesses/TestConstants.sol +++ b/l1-contracts/test/harnesses/TestConstants.sol @@ -8,7 +8,7 @@ library TestConstants { uint256 internal constant AZTEC_SLOT_DURATION = 24; uint256 internal constant AZTEC_EPOCH_DURATION = 16; uint256 internal constant AZTEC_TARGET_COMMITTEE_SIZE = 48; - uint256 internal constant AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS = 13; + uint256 internal constant AZTEC_PROOF_SUBMISSION_WINDOW = 32; uint256 internal constant AZTEC_MINIMUM_STAKE = 100e18; uint256 internal constant AZTEC_SLASHING_QUORUM = 6; uint256 internal constant AZTEC_SLASHING_ROUND_SIZE = 10; diff --git a/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol b/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol deleted file mode 100644 index 45a7985308c9..000000000000 --- a/l1-contracts/test/prover-coordination/ProofCommitmentEscrow.t.sol +++ /dev/null @@ -1,315 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright 2024 Aztec Labs. -pragma solidity >=0.8.27; - -import {Test} from "forge-std/Test.sol"; - -import {ProofCommitmentEscrow} from "@aztec/core/ProofCommitmentEscrow.sol"; -import {Errors} from "@aztec/core/libraries/Errors.sol"; -import {Timestamp} from "@aztec/core/libraries/TimeLib.sol"; -import {TestConstants} from "../harnesses/TestConstants.sol"; -import {TestERC20} from "@aztec/mock/TestERC20.sol"; - -// solhint-disable comprehensive-interface - -contract TestProofCommitmentEscrow is Test { - // solhint-disable-next-line var-name-mixedcase - ProofCommitmentEscrow internal ESCROW; - // solhint-disable-next-line var-name-mixedcase - TestERC20 internal TOKEN; - address internal prover; - uint256 internal depositAmount; - - modifier setupWithApproval(address _prover, uint256 _depositAmount) { - TOKEN.mint(_prover, _depositAmount); - vm.prank(_prover); - TOKEN.approve(address(ESCROW), _depositAmount); - - prover = _prover; - depositAmount = _depositAmount; - _; - } - - function setUp() public { - TOKEN = new TestERC20("test", "TEST", address(this)); - ESCROW = new ProofCommitmentEscrow( - TOKEN, address(this), TestConstants.AZTEC_SLOT_DURATION, TestConstants.AZTEC_EPOCH_DURATION - ); - } - - function testDeposit() public setupWithApproval(address(42), 100) { - vm.prank(prover); - ESCROW.deposit(depositAmount); - - assertEq( - TOKEN.balanceOf(address(ESCROW)), depositAmount, "Escrow balance should match deposit amount" - ); - assertEq(TOKEN.balanceOf(prover), 0, "Prover balance should be 0 after deposit"); - } - - function testCannotWithdrawWithoutMatureRequest() public setupWithApproval(address(42), 100) { - vm.prank(prover); - ESCROW.deposit(depositAmount); - uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); - - vm.prank(prover); - ESCROW.startWithdraw(depositAmount); - - vm.prank(prover); - vm.expectRevert( - abi.encodeWithSelector( - Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, - block.timestamp, - withdrawReadyAt - ) - ); - ESCROW.executeWithdraw(); - - vm.warp(block.timestamp + ESCROW.WITHDRAW_DELAY() - 1); - vm.prank(prover); - vm.expectRevert( - abi.encodeWithSelector( - Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, - block.timestamp, - withdrawReadyAt - ) - ); - ESCROW.executeWithdraw(); - } - - function testWithdrawAfterDelay() public setupWithApproval(address(42), 100) { - vm.prank(prover); - ESCROW.deposit(depositAmount); - uint256 withdrawAmount = 50; - uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); - - vm.prank(prover); - ESCROW.startWithdraw(withdrawAmount); - - vm.warp(withdrawReadyAt); - - vm.prank(prover); - ESCROW.executeWithdraw(); - - assertEq( - TOKEN.balanceOf(address(ESCROW)), - depositAmount - withdrawAmount, - "Escrow balance should be reduced after withdrawal" - ); - assertEq(TOKEN.balanceOf(prover), withdrawAmount, "Prover balance should match deposit amount"); - } - - function testCannotReplayWithdrawRequest(uint256 _withdrawAmount) - public - setupWithApproval(address(42), 100) - { - vm.prank(prover); - ESCROW.deposit(depositAmount); - uint256 withdrawAmount = bound(_withdrawAmount, 1, depositAmount); - uint256 withdrawReadyAt = block.timestamp + ESCROW.WITHDRAW_DELAY(); - - vm.prank(prover); - ESCROW.startWithdraw(withdrawAmount); - vm.warp(withdrawReadyAt); - - vm.prank(prover); - ESCROW.executeWithdraw(); - - vm.prank(prover); - ESCROW.executeWithdraw(); - - assertEq( - TOKEN.balanceOf(address(ESCROW)), - depositAmount - withdrawAmount, - "Escrow balance should be reduced after withdrawal" - ); - } - - function testOnlyOwnerCanStake(address nonOwner) public { - vm.assume(nonOwner != address(this)); - vm.prank(nonOwner); - vm.expectRevert( - abi.encodeWithSelector(Errors.ProofCommitmentEscrow__NotOwner.selector, nonOwner) - ); - ESCROW.stakeBond(address(0), 0); - } - - function testCannotStakeMoreThanProverBalance() public setupWithApproval(address(42), 100) { - vm.prank(prover); - ESCROW.deposit(depositAmount); - uint256 stakeAmount = depositAmount + 1; - - vm.expectRevert(); - ESCROW.stakeBond(prover, stakeAmount); - - assertEq( - TOKEN.balanceOf(address(ESCROW)), depositAmount, "Escrow balance should match deposit amount" - ); - assertEq(ESCROW.deposits(prover), depositAmount, "Prover balance should match deposit amount"); - } - - function testOnlyOwnerCanUnstake(address nonOwner) public { - vm.assume(nonOwner != address(this)); - vm.prank(nonOwner); - vm.expectRevert( - abi.encodeWithSelector(Errors.ProofCommitmentEscrow__NotOwner.selector, nonOwner) - ); - ESCROW.unstakeBond(address(0), 0); - } - - function testStakeAndUnstake() public setupWithApproval(address(42), 100) { - vm.prank(prover); - ESCROW.deposit(depositAmount); - uint256 stakeAmount = 50; - - ESCROW.stakeBond(prover, stakeAmount); - - assertEq( - ESCROW.deposits(prover), depositAmount - stakeAmount, "Prover balance should be reduced" - ); - - ESCROW.unstakeBond(prover, stakeAmount); - - assertEq( - ESCROW.deposits(prover), depositAmount, "Prover balance should be restored after unstake" - ); - } - - function testOverwritingStakeSlashesPreviousProver() public { - address proverA = address(42); - address proverB = address(43); - uint256 depositAmountA = 100; - uint256 stakeAmountA = 50; - uint256 depositAmountB = 200; - uint256 stakeAmountB = 100; - - TOKEN.mint(proverA, depositAmountA); - vm.prank(proverA); - TOKEN.approve(address(ESCROW), depositAmountA); - vm.prank(proverA); - ESCROW.deposit(depositAmountA); - - TOKEN.mint(proverB, depositAmountB); - vm.prank(proverB); - TOKEN.approve(address(ESCROW), depositAmountB); - vm.prank(proverB); - ESCROW.deposit(depositAmountB); - - // Prover A is staked - ESCROW.stakeBond(proverA, stakeAmountA); - - // Prover B is staked - ESCROW.stakeBond(proverB, stakeAmountB); - - // Prover A is missing the stake - uint256 expectedDepositA = depositAmountA - stakeAmountA; - assertEq( - ESCROW.deposits(proverA), - expectedDepositA, - "Prover A's deposit should reflect the slashed stake" - ); - - // Prover B gets unstaked - ESCROW.unstakeBond(proverB, stakeAmountB); - assertEq( - ESCROW.deposits(proverB), - depositAmountB, - "Prover B's deposit should be restored after unstake" - ); - assertEq( - ESCROW.deposits(proverA), expectedDepositA, "Prover A's deposit remains slashed after unstake" - ); - } - - function testWithdrawRequestOverwriting() public setupWithApproval(address(42), 100) { - uint256 withdrawAmountA = 40; - uint256 withdrawAmountB = 60; - uint256 withdrawReadyAtA = block.timestamp + ESCROW.WITHDRAW_DELAY(); - uint256 withdrawReadyAtB = block.timestamp + 2 * ESCROW.WITHDRAW_DELAY(); - - vm.prank(prover); - ESCROW.deposit(depositAmount); - - // Prover starts first withdraw request - vm.prank(prover); - ESCROW.startWithdraw(withdrawAmountA); - - // Prover starts second withdraw request before executing first - vm.warp(withdrawReadyAtA); - - vm.prank(prover); - ESCROW.startWithdraw(withdrawAmountB); - - // Attempt to execute first withdraw request after its delay - vm.prank(prover); - vm.expectRevert( - abi.encodeWithSelector( - Errors.ProofCommitmentEscrow__WithdrawRequestNotReady.selector, - withdrawReadyAtA, - withdrawReadyAtB - ) - ); - ESCROW.executeWithdraw(); - - // Execute second withdraw request after its delay - vm.warp(withdrawReadyAtB); - vm.prank(prover); - ESCROW.executeWithdraw(); - - // Assert - assertEq( - ESCROW.deposits(prover), - depositAmount - withdrawAmountB, - "Prover's deposit should be reduced by the withdrawn amount" - ); - } - - function testMinBalanceAtTime() public setupWithApproval(address(42), 100) { - uint256 withdrawAmount = 25; - Timestamp withdrawReadyAt = Timestamp.wrap(block.timestamp + ESCROW.WITHDRAW_DELAY()); - - vm.prank(prover); - ESCROW.deposit(depositAmount); - - assertEq( - ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp), prover), - depositAmount, - "Min balance should match deposit amount before any withdraw request" - ); - - assertEq( - ESCROW.minBalanceAtTime(withdrawReadyAt - Timestamp.wrap(1), prover), - depositAmount, - "Min balance should match deposit amount before withdraw request matures" - ); - - vm.prank(prover); - ESCROW.startWithdraw(withdrawAmount); - - assertEq( - ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp), prover), - depositAmount, - "Min balance should be unaffected by pending withdraw request before maturity" - ); - - assertEq( - ESCROW.minBalanceAtTime(Timestamp.wrap(block.timestamp + ESCROW.WITHDRAW_DELAY()), prover), - 0, - "Min balance should be 0 at or beyond the delay window" - ); - - vm.warp(block.timestamp + 1); - - assertEq( - ESCROW.minBalanceAtTime(withdrawReadyAt, prover), - depositAmount - withdrawAmount, - "Min balance should be 75 after withdraw request matures" - ); - - assertEq( - ESCROW.minBalanceAtTime(withdrawReadyAt + Timestamp.wrap(1), prover), - 0, - "Min balance should be 0 at or beyond the delay window" - ); - } -} diff --git a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol index 46fd1bea36c0..5488fda3b56c 100644 --- a/l1-contracts/test/validator-selection/ValidatorSelection.t.sol +++ b/l1-contracts/test/validator-selection/ValidatorSelection.t.sol @@ -2,7 +2,7 @@ // Copyright 2024 Aztec Labs. pragma solidity >=0.8.27; -import {DecoderBase} from "../decoders/Base.sol"; +import {DecoderBase} from "../base/DecoderBase.sol"; import {DataStructures} from "@aztec/core/libraries/DataStructures.sol"; import {Constants} from "@aztec/core/libraries/ConstantsGen.sol"; @@ -109,7 +109,7 @@ contract ValidatorSelectionTest is DecoderBase { aztecSlotDuration: TestConstants.AZTEC_SLOT_DURATION, aztecEpochDuration: TestConstants.AZTEC_EPOCH_DURATION, targetCommitteeSize: TestConstants.AZTEC_TARGET_COMMITTEE_SIZE, - aztecEpochProofClaimWindowInL2Slots: TestConstants.AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS, + aztecProofSubmissionWindow: TestConstants.AZTEC_PROOF_SUBMISSION_WINDOW, minimumStake: TestConstants.AZTEC_MINIMUM_STAKE, slashingQuorum: TestConstants.AZTEC_SLASHING_QUORUM, slashingRoundSize: TestConstants.AZTEC_SLASHING_ROUND_SIZE diff --git a/scripts/ci/get_e2e_jobs.sh b/scripts/ci/get_e2e_jobs.sh index e9742d06abe5..3551478a4667 100755 --- a/scripts/ci/get_e2e_jobs.sh +++ b/scripts/ci/get_e2e_jobs.sh @@ -40,7 +40,6 @@ allow_list=( "integration_l1_publisher" "e2e_cheat_codes" "e2e_prover_fake_proofs" - "e2e_prover_coordination" "e2e_lending_contract" "e2e_p2p_gossip" "kind_network_smoke" @@ -54,7 +53,7 @@ allow_list=( # Add labels from input to the allow_list, supports prefix matching # E.g: # e2e_p2p label will match e2e_p2p_gossip, e2e_p2p_rediscovery, e2e_p2p_reqresp etc. -# e2e_prover label will match e2e_prover_fake_proofs, e2e_prover_coordination etc. +# e2e_prover label will match e2e_prover_fake_proofs etc. IFS=',' read -r -a input_labels <<< "$LABELS" expanded_allow_list=() diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index f85b272fd8cb..7cd24a8e9cc2 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -417,7 +417,8 @@ export async function getProofFromSubmitProofTx( if (functionName === 'submitEpochRootProof') { const [decodedArgs] = args as readonly [ { - epochSize: bigint; + start: bigint; + end: bigint; args: readonly [Hex, Hex, Hex, Hex, Hex, Hex, Hex]; fees: readonly Hex[]; aggregationObject: Hex; diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 3c874eb768eb..98c8618d1ff0 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -4,7 +4,6 @@ import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob- import { type AztecNode, type ClientProtocolCircuitVerifier, - type EpochProofQuote, type GetContractClassLogsResponse, type GetPublicLogsResponse, type InBlock, @@ -123,14 +122,6 @@ export class AztecNodeService implements AztecNode, Traceable { this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts); } - public addEpochProofQuote(quote: EpochProofQuote): Promise { - return Promise.resolve(this.p2pClient.addEpochProofQuote(quote)); - } - - public getEpochProofQuotes(epoch: bigint): Promise { - return this.p2pClient.getEpochProofQuotes(epoch); - } - public getL2Tips() { return this.blockSource.getL2Tips(); } diff --git a/yarn-project/aztec.js/src/api/cheat_codes.ts b/yarn-project/aztec.js/src/api/cheat_codes.ts index 19e7dfd7bf95..3a8c1cde9307 100644 --- a/yarn-project/aztec.js/src/api/cheat_codes.ts +++ b/yarn-project/aztec.js/src/api/cheat_codes.ts @@ -2,8 +2,8 @@ import { type PXE } from '@aztec/circuit-types'; import { EthCheatCodes } from '@aztec/ethereum/eth-cheatcodes'; import { type L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; -import { RollupCheatCodes } from '../index.js'; import { AztecCheatCodes } from '../utils/aztec_cheatcodes.js'; +import { RollupCheatCodes } from './ethereum/cheat_codes.js'; /** * A class that provides utility functions for interacting with the chain. diff --git a/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts b/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts index 849a35977091..e4b89da94924 100644 --- a/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts +++ b/yarn-project/aztec.js/src/api/ethereum/cheat_codes.ts @@ -1,5 +1,3 @@ -import { type EpochProofClaim } from '@aztec/circuit-types'; -import { EthAddress } from '@aztec/circuits.js'; import { EthCheatCodes } from '@aztec/ethereum/eth-cheatcodes'; import { type L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'; import { createLogger } from '@aztec/foundation/log'; @@ -107,33 +105,6 @@ export class RollupCheatCodes { this.logger.warn(`Advanced ${howMany} slots up to slot ${slot} in epoch ${epoch}`); } - /** Returns the current proof claim (if any) */ - public async getProofClaim(): Promise { - // REFACTOR: This code is duplicated from l1-publisher - const { - epochToProve, - basisPointFee, - bondAmount, - bondProvider: bondProviderHex, - proposerClaimant: proposerClaimantHex, - } = await this.rollup.read.getProofClaim(); - - const bondProvider = EthAddress.fromString(bondProviderHex); - const proposerClaimant = EthAddress.fromString(proposerClaimantHex); - - if (bondProvider.isZero() && proposerClaimant.isZero() && epochToProve === 0n) { - return undefined; - } - - return { - epochToProve, - basisPointFee, - bondAmount, - bondProvider, - proposerClaimant, - }; - } - /** * Marks the specified block (or latest if none) as proven * @param maybeBlockNumber - The block number to mark as proven (defaults to latest pending) diff --git a/yarn-project/aztec.js/src/index.ts b/yarn-project/aztec.js/src/index.ts index fde9a37f04c2..410ace04e98a 100644 --- a/yarn-project/aztec.js/src/index.ts +++ b/yarn-project/aztec.js/src/index.ts @@ -41,14 +41,14 @@ export { EthAddress, Fq, Fr, + getContractClassFromArtifact, + getContractInstanceFromDeployParams, GlobalVariables, GrumpkinScalar, INITIAL_L2_BLOCK_NUM, NodeInfo, Point, PublicKeys, - getContractClassFromArtifact, - getContractInstanceFromDeployParams, } from '@aztec/circuits.js'; export { computeSecretHash } from '@aztec/circuits.js/hash'; @@ -68,12 +68,12 @@ export { Comparator, ContractClass2BlockL2Logs, EncryptedLogPayload, - EpochProofQuote, - EpochProofQuotePayload, EventMetadata, EventType, ExtendedNote, FunctionCall, + getTimestampRangeForEpoch, + HashedValues, L1Actor, L1EventPayload, L1NotePayload, @@ -82,8 +82,9 @@ export { L2Block, LogId, MerkleTreeId, + merkleTreeIds, + mockTx, Note, - HashedValues, SiblingPath, Tx, TxExecutionRequest, @@ -92,14 +93,10 @@ export { TxStatus, UnencryptedL2Log, UniqueNote, - getTimestampRangeForEpoch, - merkleTreeIds, - mockEpochProofQuote, - mockTx, type LogFilter, - type PXE, type PartialAddress, type PublicKey, + type PXE, } from '@aztec/circuit-types'; // TODO: These kinds of things have no place on our public api. @@ -120,10 +117,10 @@ export { fileURLToPath } from '@aztec/foundation/url'; // Here you *can* do `export *` as the granular api defacto exports things explicitly. // This entire index file will be deprecated at some point after we're satisfied. export * from './api/abi.js'; -export * from './api/cheat_codes.js'; -export * from './api/fee.js'; export * from './api/addresses.js'; +export * from './api/cheat_codes.js'; export * from './api/ethereum/index.js'; +export * from './api/fee.js'; export * from './api/log.js'; // Granular export, even if not in the api folder export * from './contract/index.js'; 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 43d9d04ac1b0..700cad244cc6 100644 --- a/yarn-project/aztec/src/cli/cmds/start_prover_node.ts +++ b/yarn-project/aztec/src/cli/cmds/start_prover_node.ts @@ -97,6 +97,6 @@ export async function startProverNode( signalHandlers.push(proverNode.stop.bind(proverNode)); - await proverNode.start(); + proverNode.start(); return { config: proverConfig }; } diff --git a/yarn-project/circuit-types/src/index.ts b/yarn-project/circuit-types/src/index.ts index aa7bfb3efea9..4259913bd9ed 100644 --- a/yarn-project/circuit-types/src/index.ts +++ b/yarn-project/circuit-types/src/index.ts @@ -1,8 +1,11 @@ export { CompleteAddress, GrumpkinScalar, type PartialAddress, type PublicKey } from '@aztec/circuits.js'; export * from './auth_witness.js'; export * from './body.js'; +export * from './epoch-helpers/index.js'; export * from './function_call.js'; export * from './global_variable_builder.js'; +export * from './hashed_values.js'; +export * from './in_block.js'; export * from './interfaces/index.js'; export * from './l2_block.js'; export * from './l2_block_downloader/index.js'; @@ -12,9 +15,9 @@ export * from './merkle_tree_id.js'; export * from './messaging/index.js'; export * from './mocks.js'; export * from './notes/index.js'; +export * from './nullifier_with_block_source.js'; export * from './p2p/index.js'; -export * from './hashed_values.js'; -export * from './prover_coordination/index.js'; +export * from './proving_error.js'; export * from './public_data_witness.js'; export * from './public_execution_request.js'; export * from './sibling_path/index.js'; @@ -22,8 +25,4 @@ export * from './simulation_error.js'; export * from './tx/index.js'; export * from './tx_effect.js'; export * from './tx_execution_request.js'; -export * from './in_block.js'; -export * from './nullifier_with_block_source.js'; -export * from './proving_error.js'; -export * from './epoch-helpers/index.js'; export * from './versioning.js'; diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts index 44d52d36ca9e..2e32485fffa8 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.test.ts @@ -41,7 +41,6 @@ import { } from '../logs/get_logs_response.js'; import { type LogFilter } from '../logs/log_filter.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { PublicDataWitness } from '../public_data_witness.js'; import { SiblingPath } from '../sibling_path/sibling_path.js'; import { type TxValidationResult } from '../tx/index.js'; @@ -347,15 +346,6 @@ describe('AztecNodeApiSchema', () => { expect(response).toBe('enr:-'); }); - it('addEpochProofQuote', async () => { - await context.client.addEpochProofQuote(EpochProofQuote.random()); - }); - - it('getEpochProofQuotes', async () => { - const response = await context.client.getEpochProofQuotes(1n); - expect(response).toEqual([expect.any(EpochProofQuote)]); - }); - it('addContractClass', async () => { const contractClass = await getContractClassFromArtifact(artifact); await context.client.addContractClass({ ...contractClass, unconstrainedFunctions: [], privateFunctions: [] }); @@ -612,14 +602,6 @@ class MockAztecNode implements AztecNode { getEncodedEnr(): Promise { return Promise.resolve('enr:-'); } - addEpochProofQuote(quote: EpochProofQuote): Promise { - expect(quote).toBeInstanceOf(EpochProofQuote); - return Promise.resolve(); - } - getEpochProofQuotes(epoch: bigint): Promise { - expect(epoch).toEqual(1n); - return Promise.resolve([EpochProofQuote.random()]); - } addContractClass(_contractClass: ContractClassPublic): Promise { return Promise.resolve(); } diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index 9018ea444186..bd9e0383c35c 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -37,7 +37,6 @@ import { TxScopedL2Log, } from '../logs/index.js'; import { MerkleTreeId } from '../merkle_tree_id.js'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { PublicDataWitness } from '../public_data_witness.js'; import { SiblingPath } from '../sibling_path/index.js'; import { @@ -440,18 +439,6 @@ export interface AztecNode */ 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; - /** * Adds a contract class bypassing the registerer. * TODO(#10007): Remove this method. @@ -594,10 +581,6 @@ export const AztecNodeApiSchema: ApiSchemaFor = { getEncodedEnr: z.function().returns(z.string().optional()), - addEpochProofQuote: z.function().args(EpochProofQuote.schema).returns(z.void()), - - getEpochProofQuotes: z.function().args(schemas.BigInt).returns(z.array(EpochProofQuote.schema)), - // TODO(#10007): Remove this method addContractClass: z.function().args(ContractClassPublicSchema).returns(z.void()), }; diff --git a/yarn-project/circuit-types/src/interfaces/p2p.test.ts b/yarn-project/circuit-types/src/interfaces/p2p.test.ts index 10af0c9d12f5..46d908395af9 100644 --- a/yarn-project/circuit-types/src/interfaces/p2p.test.ts +++ b/yarn-project/circuit-types/src/interfaces/p2p.test.ts @@ -1,7 +1,6 @@ import { type JsonRpcTestContext, createJsonRpcTestSetup } from '@aztec/foundation/json-rpc/test'; import { BlockAttestation } from '../p2p/block_attestation.js'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { Tx } from '../tx/tx.js'; import { type P2PApi, P2PApiSchema, type PeerInfo } from './p2p.js'; @@ -32,12 +31,6 @@ describe('P2PApiSchema', () => { expect(attestations[0]).toBeInstanceOf(BlockAttestation); }); - it('getEpochProofQuotes', async () => { - const quotes = await context.client.getEpochProofQuotes(BigInt(1)); - expect(quotes).toEqual([EpochProofQuote.empty()]); - expect(quotes[0]).toBeInstanceOf(EpochProofQuote); - }); - it('getPendingTxs', async () => { const txs = await context.client.getPendingTxs(); expect(txs[0]).toBeInstanceOf(Tx); @@ -71,16 +64,15 @@ class MockP2P implements P2PApi { expect(proposalId).toEqual('proposalId'); return Promise.resolve([BlockAttestation.empty()]); } - getEpochProofQuotes(epoch: bigint): Promise { - expect(epoch).toEqual(1n); - return Promise.resolve([EpochProofQuote.empty()]); - } + async getPendingTxs(): Promise { return [await Tx.random()]; } + getEncodedEnr(): Promise { return Promise.resolve('enr'); } + getPeers(includePending?: boolean): Promise { expect(includePending === undefined || includePending === true).toBeTruthy(); return Promise.resolve(peers); diff --git a/yarn-project/circuit-types/src/interfaces/p2p.ts b/yarn-project/circuit-types/src/interfaces/p2p.ts index d2032f9d129b..7c4083616d37 100644 --- a/yarn-project/circuit-types/src/interfaces/p2p.ts +++ b/yarn-project/circuit-types/src/interfaces/p2p.ts @@ -4,7 +4,6 @@ import { z } from 'zod'; import { BlockAttestation } from '../p2p/block_attestation.js'; import { type P2PClientType } from '../p2p/client_type.js'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { Tx } from '../tx/tx.js'; export type PeerInfo = @@ -26,14 +25,6 @@ const PeerInfoSchema = z.discriminatedUnion('status', [ /** Exposed API to the P2P module. */ export interface P2PApiWithoutAttestations { - /** - * Queries the EpochProofQuote pool for quotes for the given epoch - * - * @param epoch - the epoch to query - * @returns EpochProofQuotes - */ - getEpochProofQuotes(epoch: bigint): Promise; - /** * Returns all pending transactions in the transaction pool. * @returns An array of Txs. @@ -71,7 +62,6 @@ export const P2PApiSchema: ApiSchemaFor = { .function() .args(schemas.BigInt, optional(z.string())) .returns(z.array(BlockAttestation.schema)), - getEpochProofQuotes: z.function().args(schemas.BigInt).returns(z.array(EpochProofQuote.schema)), getPendingTxs: z.function().returns(z.array(Tx.schema)), getEncodedEnr: z.function().returns(z.string().optional()), getPeers: z.function().args(optional(z.boolean())).returns(z.array(PeerInfoSchema)), diff --git a/yarn-project/circuit-types/src/interfaces/prover-coordination.ts b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts index 396f21cf36b5..bff01672456b 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-coordination.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-coordination.ts @@ -2,7 +2,6 @@ import { type ApiSchemaFor } from '@aztec/foundation/schemas'; import { z } from 'zod'; -import { EpochProofQuote } from '../prover_coordination/index.js'; import { Tx } from '../tx/tx.js'; import { TxHash } from '../tx/tx_hash.js'; @@ -21,16 +20,9 @@ export interface ProverCoordination { * @returns The transactions, if found, 'undefined' otherwise. */ getTxsByHash(txHashes: 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; } export const ProverCoordinationApiSchema: ApiSchemaFor = { getTxByHash: z.function().args(TxHash.schema).returns(Tx.schema.optional()), getTxsByHash: z.function().args(z.array(TxHash.schema)).returns(z.array(Tx.schema)), - addEpochProofQuote: z.function().args(EpochProofQuote.schema).returns(z.void()), }; diff --git a/yarn-project/circuit-types/src/interfaces/prover-node.test.ts b/yarn-project/circuit-types/src/interfaces/prover-node.test.ts index cbb1314bd548..e552ee181a75 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-node.test.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-node.test.ts @@ -1,6 +1,5 @@ import { type JsonRpcTestContext, createJsonRpcTestSetup } from '@aztec/foundation/json-rpc/test'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { type EpochProvingJobState, type ProverNodeApi, ProverNodeApiSchema } from './prover-node.js'; describe('ProvingNodeApiSchema', () => { @@ -37,22 +36,17 @@ describe('ProvingNodeApiSchema', () => { it('prove', async () => { await context.client.prove(1); }); - - it('sendEpochProofQuote', async () => { - const quote = EpochProofQuote.empty(); - await context.client.sendEpochProofQuote(quote); - }); }); class MockProverNode implements ProverNodeApi { - getJobs(): Promise<{ uuid: string; status: EpochProvingJobState }[]> { + getJobs(): Promise<{ uuid: string; status: EpochProvingJobState; epochNumber: number }[]> { return Promise.resolve([ - { uuid: 'uuid1', status: 'initialized' }, - { uuid: 'uuid2', status: 'processing' }, - { uuid: 'uuid3', status: 'awaiting-prover' }, - { uuid: 'uuid4', status: 'publishing-proof' }, - { uuid: 'uuid5', status: 'completed' }, - { uuid: 'uuid6', status: 'failed' }, + { uuid: 'uuid1', status: 'initialized', epochNumber: 10 }, + { uuid: 'uuid2', status: 'processing', epochNumber: 10 }, + { uuid: 'uuid3', status: 'awaiting-prover', epochNumber: 10 }, + { uuid: 'uuid4', status: 'publishing-proof', epochNumber: 10 }, + { uuid: 'uuid5', status: 'completed', epochNumber: 10 }, + { uuid: 'uuid6', status: 'failed', epochNumber: 10 }, ]); } startProof(epochNumber: number): Promise { @@ -63,8 +57,4 @@ class MockProverNode implements ProverNodeApi { expect(typeof epochNumber).toBe('number'); return Promise.resolve(); } - sendEpochProofQuote(quote: EpochProofQuote): Promise { - expect(quote).toBeInstanceOf(EpochProofQuote); - return Promise.resolve(); - } } diff --git a/yarn-project/circuit-types/src/interfaces/prover-node.ts b/yarn-project/circuit-types/src/interfaces/prover-node.ts index 7fca59b4b1c8..b9191205a23d 100644 --- a/yarn-project/circuit-types/src/interfaces/prover-node.ts +++ b/yarn-project/circuit-types/src/interfaces/prover-node.ts @@ -1,13 +1,7 @@ -import { type Signature } from '@aztec/foundation/eth-signature'; import { type ApiSchemaFor, schemas } from '@aztec/foundation/schemas'; import { z } from 'zod'; -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; - -// Required by ts to export the schema of EpochProofQuote -export { type Signature }; - const EpochProvingJobState = [ 'initialized', 'processing', @@ -32,13 +26,11 @@ export type EpochProvingJobTerminalState = (typeof EpochProvingJobTerminalState) /** JSON RPC public interface to a prover node. */ export interface ProverNodeApi { - getJobs(): Promise<{ uuid: string; status: EpochProvingJobState }[]>; + getJobs(): Promise<{ uuid: string; status: EpochProvingJobState; epochNumber: number }[]>; startProof(epochNumber: number): Promise; prove(epochNumber: number): Promise; - - sendEpochProofQuote(quote: EpochProofQuote): Promise; } /** Schemas for prover node API functions. */ @@ -46,11 +38,9 @@ export const ProverNodeApiSchema: ApiSchemaFor = { getJobs: z .function() .args() - .returns(z.array(z.object({ uuid: z.string(), status: z.enum(EpochProvingJobState) }))), + .returns(z.array(z.object({ uuid: z.string(), status: z.enum(EpochProvingJobState), epochNumber: z.number() }))), startProof: z.function().args(schemas.Integer).returns(z.void()), prove: z.function().args(schemas.Integer).returns(z.void()), - - sendEpochProofQuote: z.function().args(EpochProofQuote.schema).returns(z.void()), }; diff --git a/yarn-project/circuit-types/src/interfaces/pxe.test.ts b/yarn-project/circuit-types/src/interfaces/pxe.test.ts index 261c41c24fd7..fc5411c42d3c 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.test.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.test.ts @@ -42,7 +42,6 @@ import { import { ExtendedNote, UniqueNote } from '../notes/index.js'; import { type NotesFilter } from '../notes/notes_filter.js'; import { PrivateExecutionResult } from '../private_execution_result.js'; -import { type EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { SiblingPath } from '../sibling_path/sibling_path.js'; import { Tx, TxHash, TxProvingResult, TxReceipt, TxSimulationResult } from '../tx/index.js'; import { TxEffect } from '../tx_effect.js'; @@ -306,7 +305,7 @@ describe('PXESchema', () => { }); it('getPrivateEvents', async () => { - const result = await context.client.getPrivateEvents( + const result = await context.client.getPrivateEvents<{ value: bigint }>( { abiType: { kind: 'boolean' }, eventSelector: EventSelector.random(), fieldNames: ['name'] }, 1, 1, @@ -316,7 +315,7 @@ describe('PXESchema', () => { }); it('getPublicEvents', async () => { - const result = await context.client.getPublicEvents( + const result = await context.client.getPublicEvents<{ value: bigint }>( { abiType: { kind: 'boolean' }, eventSelector: EventSelector.random(), fieldNames: ['name'] }, 1, 1, diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 67a739c55722..c152f35abb0a 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -3,7 +3,6 @@ import { CallContext, ClientIvcProof, type ContractInstanceWithAddress, - EthAddress, GasFees, GasSettings, MAX_ENQUEUED_CALLS_PER_TX, @@ -20,8 +19,7 @@ import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { makeCombinedConstantData, makeGas, makePublicCallRequest } from '@aztec/circuits.js/testing'; import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi'; import { times } from '@aztec/foundation/collection'; -import { randomBigInt, randomBytes, randomInt } from '@aztec/foundation/crypto'; -import { Signature } from '@aztec/foundation/eth-signature'; +import { randomBytes } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { ContractClassTxL2Logs, Note } from './logs/index.js'; @@ -31,8 +29,6 @@ import { PrivateCallExecutionResult, PrivateExecutionResult, } from './private_execution_result.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 { PublicSimulationOutput, Tx, TxHash, TxSimulationResult, accumulatePrivateReturnValues } from './tx/index.js'; import { TxEffect } from './tx_effect.js'; @@ -185,24 +181,6 @@ export const mockSimulatedTx = async (seed = 1) => { return new TxSimulationResult(privateExecutionResult, tx.data, output); }; -export const mockEpochProofQuote = ( - epochToProve: bigint, - validUntilSlot?: bigint, - bondAmount?: bigint, - proverAddress?: EthAddress, - basisPointFee?: number, -) => { - const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( - epochToProve, - validUntilSlot ?? randomBigInt(10000n), - bondAmount ?? randomBigInt(10000n) + 1000n, - proverAddress ?? EthAddress.random(), - basisPointFee ?? randomInt(100), - ); - 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/p2p/interface.ts b/yarn-project/circuit-types/src/p2p/interface.ts index 06a027946021..7b11d96f2e58 100644 --- a/yarn-project/circuit-types/src/p2p/interface.ts +++ b/yarn-project/circuit-types/src/p2p/interface.ts @@ -1,4 +1,3 @@ -import { EpochProofQuote } from '../prover_coordination/epoch_proof_quote.js'; import { Tx } from '../tx/tx.js'; import { BlockAttestation } from './block_attestation.js'; import { BlockProposal } from './block_proposal.js'; @@ -15,7 +14,6 @@ export const TopicTypeMap: Record = { [TopicType.tx]: Tx as unknown as typeof Gossipable, [TopicType.block_proposal]: BlockProposal as unknown as typeof Gossipable, [TopicType.block_attestation]: BlockAttestation as unknown as typeof Gossipable, - [TopicType.epoch_proof_quote]: EpochProofQuote as unknown as typeof Gossipable, }; /** @@ -27,5 +25,4 @@ export const TopicToDeserializer = { [Tx.p2pTopic]: Tx.fromBuffer, [BlockProposal.p2pTopic]: BlockProposal.fromBuffer, [BlockAttestation.p2pTopic]: BlockAttestation.fromBuffer, - [EpochProofQuote.p2pTopic]: EpochProofQuote.fromBuffer, }; diff --git a/yarn-project/circuit-types/src/p2p/topic_type.ts b/yarn-project/circuit-types/src/p2p/topic_type.ts index db8d215a5ca6..fca25ea31dac 100644 --- a/yarn-project/circuit-types/src/p2p/topic_type.ts +++ b/yarn-project/circuit-types/src/p2p/topic_type.ts @@ -17,14 +17,13 @@ export enum TopicType { tx = 'tx', block_proposal = 'block_proposal', block_attestation = 'block_attestation', - epoch_proof_quote = 'epoch_proof_quote', } export function getTopicTypeForClientType(clientType: P2PClientType) { if (clientType === P2PClientType.Full) { return Object.values(TopicType); } - return [TopicType.tx, TopicType.epoch_proof_quote]; + return [TopicType.tx]; } /** diff --git a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_claim.ts b/yarn-project/circuit-types/src/prover_coordination/epoch_proof_claim.ts deleted file mode 100644 index 3c41f0b00dfc..000000000000 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_claim.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type EthAddress } from '@aztec/circuits.js'; - -export type EpochProofClaim = { - epochToProve: bigint; - basisPointFee: bigint; - bondAmount: bigint; - bondProvider: EthAddress; - proposerClaimant: EthAddress; -}; 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 deleted file mode 100644 index 889aa030a195..000000000000 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.test.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { EthAddress } from '@aztec/circuits.js'; -import { Signature } from '@aztec/foundation/eth-signature'; -import { jsonParseWithSchema, jsonStringify } from '@aztec/foundation/json-rpc'; - -import { EpochProofQuote } from './epoch_proof_quote.js'; -import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; - -describe('epoch proof quote', () => { - let quote: EpochProofQuote; - - beforeEach(() => { - const payload = EpochProofQuotePayload.from({ - basisPointFee: 5000, - bondAmount: 1000000000000000000n, - epochToProve: 42n, - prover: EthAddress.random(), - validUntilSlot: 100n, - }); - - quote = new EpochProofQuote(payload, Signature.random()); - }); - - const checkEquivalence = (serialized: EpochProofQuote, deserialized: EpochProofQuote) => { - expect(deserialized.getSize()).toEqual(serialized.getSize()); - expect(deserialized).toEqual(serialized); - }; - - it('should serialize and deserialize from buffer', () => { - const deserialised = EpochProofQuote.fromBuffer(quote.toBuffer()); - checkEquivalence(quote, deserialised); - }); - - it('should serialize and deserialize from JSON', async () => { - const deserialised = await jsonParseWithSchema(jsonStringify(quote), EpochProofQuote.schema); - checkEquivalence(quote, deserialised); - }); -}); 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 deleted file mode 100644 index a7a816d48a3b..000000000000 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Buffer32 } from '@aztec/foundation/buffer'; -import { type Secp256k1Signer, keccak256 } from '@aztec/foundation/crypto'; -import { Signature } from '@aztec/foundation/eth-signature'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { type FieldsOf } from '@aztec/foundation/types'; - -import { z } from 'zod'; - -import { Gossipable } from '../p2p/gossipable.js'; -import { TopicType, createTopicString } from '../p2p/topic_type.js'; -import { EpochProofQuotePayload } from './epoch_proof_quote_payload.js'; - -export class EpochProofQuote extends Gossipable { - static override p2pTopic: string = createTopicString(TopicType.epoch_proof_quote); - - constructor(public readonly payload: EpochProofQuotePayload, public readonly signature: Signature) { - super(); - } - - static empty() { - return new EpochProofQuote(EpochProofQuotePayload.empty(), Signature.empty()); - } - - static random() { - return new EpochProofQuote(EpochProofQuotePayload.random(), Signature.random()); - } - - static getFields(fields: FieldsOf) { - return [fields.payload, fields.signature] as const; - } - - override p2pMessageIdentifier(): Promise { - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/8911 - return Promise.resolve(new Buffer32(keccak256(this.signature.toBuffer()))); - } - - override toBuffer(): Buffer { - return serializeToBuffer(...EpochProofQuote.getFields(this)); - } - - static fromBuffer(buf: Buffer | BufferReader): EpochProofQuote { - const reader = BufferReader.asReader(buf); - return new EpochProofQuote(reader.readObject(EpochProofQuotePayload), reader.readObject(Signature)); - } - - static get schema() { - return z - .object({ - payload: EpochProofQuotePayload.schema, - signature: Signature.schema, - }) - .transform(({ payload, signature }) => new EpochProofQuote(payload, signature)); - } - - // TODO: https://github.com/AztecProtocol/aztec-packages/issues/8911 - /** - * Creates a new quote with a signature. - * The digest provided must match what the rollup contract will produce i.e. `_hashTypedDataV4(EpochProofQuoteLib.hash(quote))` - * - * @param digest the digest of the payload that should be signed - * @param payload the actual quote - * @param signer the signer - * @returns a quote with an accompanying signature - */ - static new(digest: Buffer32, payload: EpochProofQuotePayload, signer: Secp256k1Signer): EpochProofQuote { - if (!payload.prover.equals(signer.address)) { - throw new Error(`Quote prover does not match signer. Prover [${payload.prover}], Signer [${signer.address}]`); - } - const signature = signer.sign(digest); - const quote = new EpochProofQuote(payload, signature); - return quote; - } - - toViemArgs() { - return { - quote: this.payload.toViemArgs(), - signature: this.signature.toViemSignature(), - }; - } - - toInspect() { - return { - signature: this.signature.toString(), - ...this.payload.toInspect(), - }; - } - - /** - * Get the size of the epoch proof quote in bytes. - * @returns The size of the epoch proof quote in bytes. - */ - getSize(): number { - return this.payload.getSize() + this.signature.getSize(); - } -} 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 deleted file mode 100644 index 4e98de66f28c..000000000000 --- a/yarn-project/circuit-types/src/prover_coordination/epoch_proof_quote_payload.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { EpochProofQuoteViemArgs } from '@aztec/ethereum'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { schemas } from '@aztec/foundation/schemas'; -import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; -import { type FieldsOf } from '@aztec/foundation/types'; - -import omit from 'lodash.omit'; -import { inspect } from 'util'; -import { z } from 'zod'; - -// Required so typescript can properly annotate the exported schema -export { type EthAddress }; - -export class EpochProofQuotePayload { - // Cached values - private asBuffer: Buffer | undefined; - private size: number | undefined; - - constructor( - public readonly epochToProve: bigint, - public readonly validUntilSlot: bigint, - public readonly bondAmount: bigint, - public readonly prover: EthAddress, - public readonly basisPointFee: number, - ) { - if (basisPointFee < 0 || basisPointFee > 10000) { - throw new Error(`Invalid basisPointFee ${basisPointFee}`); - } - } - - static empty() { - return new EpochProofQuotePayload(0n, 0n, 0n, EthAddress.ZERO, 0); - } - - static random() { - return new EpochProofQuotePayload( - BigInt(Math.floor(Math.random() * 1e3)), - BigInt(Math.floor(Math.random() * 1e6)), - BigInt(Math.floor(Math.random() * 1e9)), - EthAddress.random(), - Math.floor(Math.random() * 10000), - ); - } - - static getFields(fields: FieldsOf) { - return [ - fields.epochToProve, - fields.validUntilSlot, - fields.bondAmount, - fields.prover, - fields.basisPointFee, - ] as const; - } - - toBuffer(): Buffer { - // We cache the buffer to avoid recalculating it - if (this.asBuffer) { - return this.asBuffer; - } - this.asBuffer = serializeToBuffer(...EpochProofQuotePayload.getFields(this)); - this.size = this.asBuffer.length; - return this.asBuffer; - } - - static fromBuffer(buf: Buffer | BufferReader): EpochProofQuotePayload { - const reader = BufferReader.asReader(buf); - return new EpochProofQuotePayload( - reader.readUInt256(), - reader.readUInt256(), - reader.readUInt256(), - reader.readObject(EthAddress), - reader.readNumber(), - ); - } - - static from(fields: FieldsOf): EpochProofQuotePayload { - return new EpochProofQuotePayload( - fields.epochToProve, - fields.validUntilSlot, - fields.bondAmount, - fields.prover, - fields.basisPointFee, - ); - } - - toJSON() { - return omit(this, 'asBuffer', 'size'); - } - - static get schema() { - return z - .object({ - epochToProve: schemas.BigInt, - validUntilSlot: schemas.BigInt, - bondAmount: schemas.BigInt, - prover: schemas.EthAddress, - basisPointFee: schemas.Integer, - }) - .transform(EpochProofQuotePayload.from); - } - - toViemArgs(): EpochProofQuoteViemArgs { - return { - epochToProve: this.epochToProve, - validUntilSlot: this.validUntilSlot, - bondAmount: this.bondAmount, - prover: this.prover.toString(), - basisPointFee: this.basisPointFee, - }; - } - - toInspect() { - return { - epochToProve: Number(this.epochToProve), - validUntilSlot: this.validUntilSlot.toString(), - bondAmount: this.bondAmount.toString(), - prover: this.prover.toString(), - basisPointFee: this.basisPointFee, - }; - } - - getSize(): number { - // We cache size to avoid recalculating it - if (this.size) { - return this.size; - } - // Size is cached when calling toBuffer - this.toBuffer(); - return this.size!; - } - - [inspect.custom](): string { - return `EpochProofQuotePayload { epochToProve: ${this.epochToProve}, validUntilSlot: ${this.validUntilSlot}, bondAmount: ${this.bondAmount}, prover: ${this.prover}, basisPointFee: ${this.basisPointFee} }`; - } -} diff --git a/yarn-project/circuit-types/src/prover_coordination/index.ts b/yarn-project/circuit-types/src/prover_coordination/index.ts deleted file mode 100644 index 33b8a68050b7..000000000000 --- a/yarn-project/circuit-types/src/prover_coordination/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './epoch_proof_quote.js'; -export * from './epoch_proof_quote_payload.js'; -export * from './epoch_proof_claim.js'; diff --git a/yarn-project/end-to-end/bootstrap.sh b/yarn-project/end-to-end/bootstrap.sh index 51b3cd813cfb..a65ec8c0550e 100755 --- a/yarn-project/end-to-end/bootstrap.sh +++ b/yarn-project/end-to-end/bootstrap.sh @@ -61,7 +61,6 @@ function test_cmds { # echo "$run_test simple-flake e2e_p2p/upgrade_governance_proposer" echo "$run_test simple e2e_private_voting_contract" # echo "FAKE_PROOFS=1 $run_test simple-flake e2e_prover/full" - echo "$run_test simple e2e_prover_coordination" echo "$run_test simple e2e_public_testnet_transfer" echo "$run_test simple e2e_state_vars" echo "$run_test simple e2e_static_calls" diff --git a/yarn-project/end-to-end/scripts/e2e_test_config.yml b/yarn-project/end-to-end/scripts/e2e_test_config.yml index 3ebb3d8e2e17..13bc4781cde6 100644 --- a/yarn-project/end-to-end/scripts/e2e_test_config.yml +++ b/yarn-project/end-to-end/scripts/e2e_test_config.yml @@ -53,7 +53,6 @@ tests: # TODO reenable in https://github.com/AztecProtocol/aztec-packages/pull/9727 # e2e_pending_note_hashes_contract: {} e2e_private_voting_contract: {} - e2e_prover_coordination: {} e2e_prover_fake_proofs: test_path: 'e2e_prover/full.test.ts' env: diff --git a/yarn-project/end-to-end/src/e2e_block_building.test.ts b/yarn-project/end-to-end/src/e2e_block_building.test.ts index 6a4f70d45fbb..9eed83812b14 100644 --- a/yarn-project/end-to-end/src/e2e_block_building.test.ts +++ b/yarn-project/end-to-end/src/e2e_block_building.test.ts @@ -58,7 +58,7 @@ describe('e2e_block_building', () => { let cheatCodes: CheatCodes; let teardown: () => Promise; - const { aztecEpochProofClaimWindowInL2Slots } = getL1ContractsConfigEnvVars(); + const { aztecProofSubmissionWindow } = getL1ContractsConfigEnvVars(); afterEach(() => { jest.restoreAllMocks(); @@ -574,10 +574,9 @@ describe('e2e_block_building', () => { expect(tx2.blockNumber).toEqual(initialBlockNumber + 2); expect(await contract.methods.summed_values(ownerAddress).simulate()).toEqual(51n); - // Now move to a new epoch and past the proof claim window to cause a reorg - logger.info('Advancing past the proof claim window'); + logger.info('Advancing past the proof submission window'); await cheatCodes.rollup.advanceToNextEpoch(); - await cheatCodes.rollup.advanceSlots(aztecEpochProofClaimWindowInL2Slots + 1); // off-by-one? + await cheatCodes.rollup.advanceSlots(aztecProofSubmissionWindow + 1); // Wait until the sequencer kicks out tx1 logger.info(`Waiting for node to prune tx1`); diff --git a/yarn-project/end-to-end/src/e2e_epochs.test.ts b/yarn-project/end-to-end/src/e2e_epochs.test.ts index 6de0128183c2..67e0d7c35a87 100644 --- a/yarn-project/end-to-end/src/e2e_epochs.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs.test.ts @@ -46,7 +46,7 @@ describe('e2e_epochs', () => { aztecEpochDuration: EPOCH_DURATION_IN_L2_SLOTS, aztecSlotDuration: L1_BLOCK_TIME_IN_S * L2_SLOT_DURATION_IN_L1_SLOTS, ethereumSlotDuration: L1_BLOCK_TIME_IN_S, - aztecEpochProofClaimWindowInL2Slots: EPOCH_DURATION_IN_L2_SLOTS / 2, + aztecProofSubmissionWindow: EPOCH_DURATION_IN_L2_SLOTS * 2 - 1, minTxsPerBlock: 0, realProofs: false, startProverNode: true, @@ -124,15 +124,15 @@ describe('e2e_epochs', () => { }; it('does not allow submitting proof after epoch end', async () => { - await waitUntilEpochStarts(1); - const blockNumberAtEndOfEpoch0 = Number(await rollup.getBlockNumber()); - logger.info(`Starting epoch 1 after L2 block ${blockNumberAtEndOfEpoch0}`); - // Hold off prover tx until end of next epoch! const [epoch2Start] = getTimestampRangeForEpoch(2n, constants); proverDelayer.pauseNextTxUntilTimestamp(epoch2Start); logger.info(`Delayed prover tx until epoch 2 starts at ${epoch2Start}`); + await waitUntilEpochStarts(1); + const blockNumberAtEndOfEpoch0 = Number(await rollup.getBlockNumber()); + logger.info(`Starting epoch 1 after L2 block ${blockNumberAtEndOfEpoch0}`); + // Wait until the last block of epoch 1 is published and then hold off the sequencer. // Note that the tx below will block the sequencer until it times out // the txPropagationMaxQueryAttempts until #10824 is fixed. @@ -156,7 +156,7 @@ describe('e2e_epochs', () => { logger.info(`Test succeeded`); }); - it('submits proof claim alone if there are no txs to build a block', async () => { + it('submits proof even if there are no txs to build a block', async () => { await context.sequencer?.updateSequencerConfig({ minTxsPerBlock: 1 }); await waitUntilEpochStarts(1); // Sleep to make sure any pending blocks are published diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index 34c5f9dce4c5..46d43c5479c4 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -88,9 +88,8 @@ export class P2PNetworkTest { }, { aztecEpochDuration: initialValidatorConfig.aztecEpochDuration ?? l1ContractsConfig.aztecEpochDuration, - aztecEpochProofClaimWindowInL2Slots: - initialValidatorConfig.aztecEpochProofClaimWindowInL2Slots ?? - l1ContractsConfig.aztecEpochProofClaimWindowInL2Slots, + aztecProofSubmissionWindow: + initialValidatorConfig.aztecProofSubmissionWindow ?? l1ContractsConfig.aztecProofSubmissionWindow, assumeProvenThrough: assumeProvenThrough ?? Number.MAX_SAFE_INTEGER, initialValidators: [], }, diff --git a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts index f830b8fc1b0f..61f71100093e 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/slashing.test.ts @@ -2,6 +2,7 @@ import { type AztecNodeService } from '@aztec/aztec-node'; import { sleep } from '@aztec/aztec.js'; import { RollupAbi, SlashFactoryAbi, SlasherAbi, SlashingProposerAbi } from '@aztec/l1-artifacts'; +import { jest } from '@jest/globals'; import fs from 'fs'; import { getAddress, getContract, parseEventLogs } from 'viem'; @@ -10,6 +11,8 @@ import { createNodes } from '../fixtures/setup_p2p_test.js'; import { P2PNetworkTest } from './p2p_network.js'; import { createPXEServiceAndSubmitTransactions } from './shared.js'; +jest.setTimeout(1000000); + // Don't set this to a higher value than 9 because each node will use a different L1 publisher account and anvil seeds const NUM_NODES = 4; const BOOT_NODE_UDP_PORT = 40600; @@ -33,7 +36,7 @@ describe('e2e_p2p_slashing', () => { metricsPort: shouldCollectMetrics(), initialConfig: { aztecEpochDuration: 1, - aztecEpochProofClaimWindowInL2Slots: 1, + aztecProofSubmissionWindow: 2, slashingQuorum, slashingRoundSize, }, 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 5619dc6c4639..021e4360b7f3 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 @@ -8,7 +8,7 @@ import { type DeployL1Contracts, EthAddress, type Fq, - Fr, + type Fr, type Logger, type PXE, createLogger, @@ -264,17 +264,13 @@ export class FullProverTest { ...this.context.aztecNodeConfig, proverCoordinationNodeUrl: undefined, dataDirectory: undefined, - proverId: new Fr(81), + proverId: this.proverAddress.toField(), realProofs: this.realProofs, proverAgentCount: 2, publisherPrivateKey: `0x${proverNodePrivateKey!.toString('hex')}`, proverNodeMaxPendingJobs: 100, proverNodeMaxParallelBlocksPerEpoch: 32, proverNodePollingIntervalMs: 100, - quoteProviderBasisPointFee: 100, - quoteProviderBondAmount: 1000n, - proverMinimumEscrowAmount: 3000n, - proverTargetEscrowAmount: 6000n, txGatheringTimeoutMs: 60000, txGatheringIntervalMs: 1000, txGatheringMaxParallelRequests: 100, @@ -284,7 +280,7 @@ export class FullProverTest { archiver: archiver as Archiver, blobSinkClient, }); - await this.proverNode.start(); + this.proverNode.start(); this.logger.warn(`Proofs are now enabled`); return this; diff --git a/yarn-project/end-to-end/src/e2e_prover/full.test.ts b/yarn-project/end-to-end/src/e2e_prover/full.test.ts index c89812cc3572..2733b99ec5fc 100644 --- a/yarn-project/end-to-end/src/e2e_prover/full.test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/full.test.ts @@ -1,7 +1,7 @@ -import { type AztecAddress, EthAddress, retryUntil } from '@aztec/aztec.js'; +import { type AztecAddress, EthAddress } from '@aztec/aztec.js'; import { getTestData, isGenerateTestDataEnabled } from '@aztec/foundation/testing'; import { updateProtocolCircuitSampleInputs } from '@aztec/foundation/testing/files'; -import { RewardDistributorAbi, RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; +import { FeeJuicePortalAbi, RewardDistributorAbi, RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; import TOML from '@iarna/toml'; import '@jest/globals'; @@ -24,8 +24,9 @@ describe('full_prover', () => { let recipient: AztecAddress; let rollup: GetContractReturnType>; - let feeJuice: GetContractReturnType>; let rewardDistributor: GetContractReturnType>; + let feeJuiceToken: GetContractReturnType>; + let feeJuicePortal: GetContractReturnType>; beforeAll(async () => { await t.applyBaseSnapshots(); @@ -42,17 +43,23 @@ describe('full_prover', () => { client: t.l1Contracts.publicClient, }); - feeJuice = getContract({ - abi: TestERC20Abi, - address: t.l1Contracts.l1ContractAddresses.feeJuiceAddress.toString(), - client: t.l1Contracts.publicClient, - }); - rewardDistributor = getContract({ abi: RewardDistributorAbi, address: t.l1Contracts.l1ContractAddresses.rewardDistributorAddress.toString(), client: t.l1Contracts.publicClient, }); + + feeJuicePortal = getContract({ + abi: FeeJuicePortalAbi, + address: t.l1Contracts.l1ContractAddresses.feeJuicePortalAddress.toString(), + client: t.l1Contracts.publicClient, + }); + + feeJuiceToken = getContract({ + abi: TestERC20Abi, + address: t.l1Contracts.l1ContractAddresses.feeJuiceAddress.toString(), + client: t.l1Contracts.publicClient, + }); }, 60_000); afterAll(async () => { @@ -68,6 +75,17 @@ describe('full_prover', () => { async () => { logger.info(`Starting test for public and private transfer`); + const balance = await feeJuiceToken.read.balanceOf([feeJuicePortal.address]); + logger.info(`Balance of fee juice token: ${balance}`); + + expect(balance).toBeGreaterThan(0n); + + const canonicalAddress = await feeJuicePortal.read.canonicalRollup(); + logger.info(`Canonical address: ${canonicalAddress}`); + expect(canonicalAddress.toLowerCase()).toBe( + t.l1Contracts.l1ContractAddresses.rollupAddress.toString().toLowerCase(), + ); + // Create the two transactions const privateBalance = await provenAssets[0].methods.balance_of_private(sender).simulate(); const privateSendAmount = privateBalance / 10n; @@ -114,50 +132,45 @@ describe('full_prover', () => { logger.info(`Advancing from epoch ${epoch} to next epoch`); await cheatCodes.rollup.advanceToNextEpoch(); - const balanceBeforeCoinbase = await feeJuice.read.balanceOf([COINBASE_ADDRESS.toString()]); - const balanceBeforeProver = await feeJuice.read.balanceOf([t.proverAddress.toString()]); - - // Wait until the prover node submits a quote - logger.info(`Waiting for prover node to submit quote for epoch ${epoch}`); - await retryUntil(() => t.aztecNode.getEpochProofQuotes(epoch).then(qs => qs.length > 0), 'quote', 60, 1); - - // Send another tx so the sequencer can assemble a block that includes the prover node claim - // so the prover node starts proving - logger.info(`Sending tx to trigger a new block that includes the quote from the prover node`); - const sendOpts = { skipPublicSimulation: true }; - await provenAssets[0].methods - .transfer(recipient, privateSendAmount) - .send(sendOpts) - .wait({ timeout: 300, interval: 10 }); - tokenSim.transferPrivate(sender, recipient, privateSendAmount); - - // Expect the block to have a claim - const claim = await cheatCodes.rollup.getProofClaim(); - expect(claim).toBeDefined(); - expect(claim?.epochToProve).toEqual(epoch); + const rewardsBeforeCoinbase = await rollup.read.getSequencerRewards([COINBASE_ADDRESS.toString()]); + const rewardsBeforeProver = await rollup.read.getSpecificProverRewardsForEpoch([ + epoch, + t.proverAddress.toString(), + ]); + const oldProvenBlockNumber = await rollup.read.getProvenBlockNumber(); // And wait for the first pair of txs to be proven logger.info(`Awaiting proof for the previous epoch`); await Promise.all(txs.map(tx => tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 3000 }))); - const provenBn = await rollup.read.getProvenBlockNumber(); - const balanceAfterCoinbase = await feeJuice.read.balanceOf([COINBASE_ADDRESS.toString()]); - const balanceAfterProver = await feeJuice.read.balanceOf([t.proverAddress.toString()]); + const newProvenBlockNumber = await rollup.read.getProvenBlockNumber(); + expect(newProvenBlockNumber).toBeGreaterThan(oldProvenBlockNumber); + expect(await rollup.read.getPendingBlockNumber()).toBe(newProvenBlockNumber); + + logger.info(`checking rewards for coinbase: ${COINBASE_ADDRESS.toString()}`); + const rewardsAfterCoinbase = await rollup.read.getSequencerRewards([COINBASE_ADDRESS.toString()]); + expect(rewardsAfterCoinbase).toBeGreaterThan(rewardsBeforeCoinbase); + + const rewardsAfterProver = await rollup.read.getSpecificProverRewardsForEpoch([ + epoch, + t.proverAddress.toString(), + ]); + expect(rewardsAfterProver).toBeGreaterThan(rewardsBeforeProver); + const blockReward = (await rewardDistributor.read.BLOCK_REWARD()) as bigint; const fees = ( - await Promise.all([t.aztecNode.getBlock(Number(provenBn - 1n)), t.aztecNode.getBlock(Number(provenBn))]) + await Promise.all([ + t.aztecNode.getBlock(Number(newProvenBlockNumber - 1n)), + t.aztecNode.getBlock(Number(newProvenBlockNumber)), + ]) ).map(b => b!.header.totalFees.toBigInt()); - const rewards = fees.map(fee => fee + blockReward); - const toProver = rewards - .map(reward => (reward * claim!.basisPointFee) / 10_000n) - .reduce((acc, fee) => acc + fee, 0n); - const toCoinbase = rewards.reduce((acc, reward) => acc + reward, 0n) - toProver; + const totalRewards = fees.map(fee => fee + blockReward).reduce((acc, reward) => acc + reward, 0n); + const sequencerGain = rewardsAfterCoinbase - rewardsBeforeCoinbase; + const proverGain = rewardsAfterProver - rewardsBeforeProver; - expect(provenBn + 1n).toBe(await rollup.read.getPendingBlockNumber()); - expect(balanceAfterCoinbase).toBe(balanceBeforeCoinbase + toCoinbase); - expect(balanceAfterProver).toBe(balanceBeforeProver + toProver); - expect(claim!.bondProvider).toEqual(t.proverAddress); + // May be less than totalRewards due to burn. + expect(sequencerGain + proverGain).toBeLessThanOrEqual(totalRewards); }, TIMEOUT, ); @@ -223,25 +236,6 @@ describe('full_prover', () => { logger.info(`Advancing from epoch ${epoch} to next epoch`); await cheatCodes.rollup.advanceToNextEpoch(); - // Wait until the prover node submits a quote - logger.info(`Waiting for prover node to submit quote for epoch ${epoch}`); - await retryUntil(() => t.aztecNode.getEpochProofQuotes(epoch).then(qs => qs.length > 0), 'quote', 60, 1); - - // Send another tx so the sequencer can assemble a block that includes the prover node claim - // so the prover node starts proving - logger.info(`Sending tx to trigger a new block that includes the quote from the prover node`); - const sendOpts = { skipPublicSimulation: true }; - await provenAssets[0].methods - .transfer(recipient, privateSendAmount) - .send(sendOpts) - .wait({ timeout: 300, interval: 10 }); - tokenSim.transferPrivate(sender, recipient, privateSendAmount); - - // Expect the block to have a claim - const claim = await cheatCodes.rollup.getProofClaim(); - expect(claim).toBeDefined(); - expect(claim?.epochToProve).toEqual(epoch); - // And wait for the first pair of txs to be proven logger.info(`Awaiting proof for the previous epoch`); await Promise.all(txs.map(tx => tx.wait({ timeout: 300, interval: 10, proven: true, provenTimeout: 1500 }))); diff --git a/yarn-project/end-to-end/src/e2e_simple.test.ts b/yarn-project/end-to-end/src/e2e_simple.test.ts index 390ba6ff3a40..f2a1e59b4d7d 100644 --- a/yarn-project/end-to-end/src/e2e_simple.test.ts +++ b/yarn-project/end-to-end/src/e2e_simple.test.ts @@ -1,3 +1,4 @@ +import type { AztecNodeConfig } from '@aztec/aztec-node'; import { ContractDeployer, Fr, type Wallet } from '@aztec/aztec.js'; // eslint-disable-next-line no-restricted-imports import { EthAddress } from '@aztec/foundation/eth-address'; @@ -13,6 +14,7 @@ describe('e2e_simple', () => { let owner: Wallet; let teardown: () => Promise; + let config: AztecNodeConfig; afterEach(() => { jest.restoreAllMocks(); @@ -25,6 +27,7 @@ describe('e2e_simple', () => { ({ teardown, wallets: [owner], + config, } = await setup(1, { customForwarderContractAddress: EthAddress.ZERO, archiverPollingIntervalMS: 200, @@ -32,6 +35,11 @@ describe('e2e_simple', () => { worldStateBlockCheckIntervalMS: 200, blockCheckIntervalMS: 200, minTxsPerBlock: 1, + aztecEpochDuration: 8, + aztecProofSubmissionWindow: 16, + aztecSlotDuration: 12, + ethereumSlotDuration: 12, + startProverNode: true, })); }); @@ -47,7 +55,9 @@ describe('e2e_simple', () => { skipClassRegistration: true, skipPublicDeployment: true, }); - const tx = await provenTx.send().wait(); + const tx = await provenTx + .send() + .wait({ proven: true, provenTimeout: config.aztecProofSubmissionWindow * config.aztecSlotDuration }); expect(tx.blockNumber).toBeDefined(); }); }); diff --git a/yarn-project/end-to-end/src/fixtures/utils.ts b/yarn-project/end-to-end/src/fixtures/utils.ts index e6a6a32c968a..5437f99efb1b 100644 --- a/yarn-project/end-to-end/src/fixtures/utils.ts +++ b/yarn-project/end-to-end/src/fixtures/utils.ts @@ -519,6 +519,10 @@ export async function setup( await aztecNode?.stop(); } + if (proverNode) { + await proverNode.stop(); + } + if (acvmConfig?.cleanup) { // remove the temp directory created for the acvm logger.verbose(`Cleaning up ACVM state`); @@ -714,7 +718,6 @@ export async function createAndSyncProverNode( // Disable stopping the aztec node as the prover coordination test will kill it otherwise // This is only required when stopping the prover node for testing const aztecNodeWithoutStop = { - addEpochProofQuote: aztecNode.addEpochProofQuote.bind(aztecNode), getTxByHash: aztecNode.getTxByHash.bind(aztecNode), getTxsByHash: aztecNode.getTxsByHash.bind(aztecNode), stop: () => Promise.resolve(), @@ -739,10 +742,6 @@ export async function createAndSyncProverNode( proverNodeMaxPendingJobs: 10, proverNodeMaxParallelBlocksPerEpoch: 32, proverNodePollingIntervalMs: 200, - quoteProviderBasisPointFee: 100, - quoteProviderBondAmount: 1000n, - proverMinimumEscrowAmount: 1000n, - proverTargetEscrowAmount: 2000n, txGatheringTimeoutMs: 60000, txGatheringIntervalMs: 1000, txGatheringMaxParallelRequests: 100, @@ -755,7 +754,7 @@ export async function createAndSyncProverNode( archiver: archiver as Archiver, l1TxUtils, }); - await proverNode.start(); + proverNode.start(); return proverNode; } diff --git a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts b/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts deleted file mode 100644 index c20dacbdb646..000000000000 --- a/yarn-project/end-to-end/src/prover-coordination/e2e_prover_coordination.test.ts +++ /dev/null @@ -1,486 +0,0 @@ -import { getSchnorrAccount } from '@aztec/accounts/schnorr'; -import { createAccount } from '@aztec/accounts/testing'; -import { - type AccountWalletWithSecretKey, - EpochProofQuote, - EpochProofQuotePayload, - type Logger, - TxStatus, - createLogger, - retryUntil, - sleep, -} from '@aztec/aztec.js'; -import { type AztecAddress, EthAddress } from '@aztec/circuits.js'; -import { Buffer32 } from '@aztec/foundation/buffer'; -import { times } from '@aztec/foundation/collection'; -import { Secp256k1Signer, keccak256, randomBigInt, randomInt } from '@aztec/foundation/crypto'; -import { ProofCommitmentEscrowAbi, RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; -import { StatefulTestContract, StatefulTestContractArtifact } from '@aztec/noir-contracts.js/StatefulTest'; -import { createPXEService, getPXEServiceConfig } from '@aztec/pxe'; - -import { - type Account, - type Chain, - type GetContractReturnType, - type HttpTransport, - type PublicClient, - type WalletClient, - createWalletClient, - getAddress, - getContract, - http, -} from 'viem'; -import { privateKeyToAccount } from 'viem/accounts'; -import { foundry } from 'viem/chains'; - -import { - type ISnapshotManager, - type SubsystemsContext, - addAccounts, - createSnapshotManager, -} from '../fixtures/snapshot_manager.js'; - -describe('e2e_prover_coordination', () => { - let ctx: SubsystemsContext; - let wallet: AccountWalletWithSecretKey; - let recipient: AztecAddress; - let contract: StatefulTestContract; - let rollupContract: GetContractReturnType>; - let publicClient: PublicClient; - let publisherAddress: EthAddress; - let feeJuiceContract: GetContractReturnType>; - let escrowContract: GetContractReturnType< - typeof ProofCommitmentEscrowAbi, - WalletClient - >; - - let proverSigner: Secp256k1Signer; - let proverWallet: WalletClient; - - let logger: Logger; - let snapshotManager: ISnapshotManager; - - beforeEach(async () => { - logger = createLogger('e2e:prover_coordination'); - snapshotManager = createSnapshotManager( - `prover_coordination/e2e_prover_coordination`, - process.env.E2E_DATA_PATH, - { startProverNode: true }, - { assumeProvenThrough: undefined }, - ); - - await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => { - const wallets = await Promise.all( - accountKeys.map(async ak => { - const account = await getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1); - return account.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(); - - // Don't run the prover node work loop - we manually control within this test - await ctx.proverNode!.stop(); - - publicClient = ctx.deployL1ContractsValues.publicClient; - publisherAddress = ctx.aztecNode.getSequencer()?.forwarderAddress ?? EthAddress.ZERO; - expect(publisherAddress).not.toEqual(EthAddress.ZERO); - rollupContract = getContract({ - address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()), - abi: RollupAbi, - client: ctx.deployL1ContractsValues.walletClient, - }); - feeJuiceContract = getContract({ - address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.feeJuiceAddress.toString()), - abi: TestERC20Abi, - client: ctx.deployL1ContractsValues.walletClient, - }); - - // Create a prover wallet - const proverKey = Buffer32.random(); - proverSigner = new Secp256k1Signer(proverKey); - proverWallet = createWalletClient({ - account: privateKeyToAccount(proverKey.toString()), - chain: foundry, - transport: http(ctx.aztecNodeConfig.l1RpcUrl), - }); - - const escrowAddress = await rollupContract.read.PROOF_COMMITMENT_ESCROW(); - escrowContract = getContract({ - address: getAddress(escrowAddress.toString()), - abi: ProofCommitmentEscrowAbi, - client: ctx.deployL1ContractsValues.walletClient, - }); - - // Ensure the prover has enough funds to put in escrow - await performEscrow(10000000n); - }); - - afterEach(async () => { - await snapshotManager.teardown(); - }); - - const expectProofClaimOnL1 = async (expected: { - epochToProve: bigint; - basisPointFee: number; - bondAmount: bigint; - proposer: EthAddress; - prover: EthAddress; - }) => { - const { epochToProve, basisPointFee, bondAmount, bondProvider, proposerClaimant } = - await rollupContract.read.getProofClaim(); - expect(epochToProve).toEqual(expected.epochToProve); - expect(basisPointFee).toEqual(BigInt(expected.basisPointFee)); - expect(bondAmount).toEqual(expected.bondAmount); - expect(bondProvider).toEqual(expected.prover.toChecksumString()); - expect(proposerClaimant).toEqual(expected.proposer.toChecksumString()); - }; - - const performEscrow = async (amount: bigint) => { - // Fund with ether - await ctx.cheatCodes.eth.setBalance(proverSigner.address, 10_000n * 10n ** 18n); - // Fund with fee juice - await feeJuiceContract.write.mint([proverWallet.account.address, amount]); - - // Approve the escrow contract to spend our funds - await feeJuiceContract.write.approve([escrowContract.address, amount], { - account: proverWallet.account, - }); - - // Deposit the funds into the escrow contract - await escrowContract.write.deposit([amount], { - account: proverWallet.account, - }); - }; - - 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().catch(e => { - if (e instanceof Error && e.message.includes('NoEpochToProve')) { - return undefined; - } - }); - }; - - const expectTips = async (expected: { pending: bigint; proven: bigint }) => { - const tips = await ctx.cheatCodes.rollup.getTips(); - expect(tips.pending).toEqual(expected.pending); - expect(tips.proven).toEqual(expected.proven); - }; - - 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(ctx.aztecNodeConfig.aztecEpochDuration) - (slot % BigInt(ctx.aztecNodeConfig.aztecEpochDuration)) + 1n; - const timeToNextEpoch = slotsUntilNextEpoch * BigInt(ctx.aztecNodeConfig.aztecSlotDuration); - const l1Timestamp = await getL1Timestamp(); - await ctx.cheatCodes.eth.warp(Number(l1Timestamp + timeToNextEpoch)); - await logState(); - }; - - const makeEpochProofQuote = async ({ - epochToProve, - validUntilSlot, - bondAmount, - basisPointFee, - signer, - }: { - epochToProve: bigint; - validUntilSlot?: bigint; - bondAmount?: bigint; - basisPointFee?: number; - signer?: Secp256k1Signer; - }) => { - signer ??= new Secp256k1Signer(Buffer32.fromBuffer(keccak256(Buffer.from('cow')))); - const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload( - epochToProve, - validUntilSlot ?? randomBigInt(10000n), - bondAmount ?? randomBigInt(10000n) + 1000n, - signer.address, - basisPointFee ?? randomInt(100), - ); - const digest = await rollupContract.read.quoteToDigest([quotePayload.toViemArgs()]); - - return EpochProofQuote.new(Buffer32.fromString(digest), quotePayload, signer); - }; - - 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 - - // Ensure the prover has enough funds to in escrow - await performEscrow(10000000n); - - // Here we are creating a proof quote for epoch 0 - const quoteForEpoch0 = await makeEpochProofQuote({ - epochToProve: 0n, - validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration + 10), - bondAmount: 10000n, - basisPointFee: 1, - signer: proverSigner, - }); - - // 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 we can claim the current epoch - await expectProofClaimOnL1({ ...quoteForEpoch0.payload, proposer: publisherAddress }); - - // Now go to epoch 1 - await advanceToNextEpoch(); - - 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.payload, proposer: publisherAddress }); - - // now 'prove' epoch 0 - await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); - - 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 = await Promise.all( - times(3, (i: number) => - makeEpochProofQuote({ - epochToProve: 1n, - validUntilSlot: currentSlot + 2n, - bondAmount: 10000n, - basisPointFee: 10 + i, - signer: proverSigner, - }), - ), - ); - - const proofQuoteInvalidSlot = await makeEpochProofQuote({ - epochToProve: 1n, - validUntilSlot: 3n, - bondAmount: 10000n, - basisPointFee: 1, - signer: proverSigner, - }); - - const proofQuoteInvalidEpoch = await makeEpochProofQuote({ - epochToProve: 4n, - validUntilSlot: currentSlot + 4n, - bondAmount: 10000n, - basisPointFee: 2, - signer: proverSigner, - }); - - const proofQuoteInsufficientBond = await makeEpochProofQuote({ - epochToProve: 1n, - validUntilSlot: currentSlot + 4n, - bondAmount: 0n, - basisPointFee: 3, - signer: proverSigner, - }); - - const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInsufficientBond]; - - 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.payload, proposer: 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.payload, proposer: 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.payload, proposer: publisherAddress }); - }); - - it('Can claim proving rights after a prune', async () => { - await logState(); - - const tips = await ctx.cheatCodes.rollup.getTips(); - - let currentPending = tips.pending; - let currentProven = tips.proven; - // Here we are creating a proof quote for epoch 0 - const quoteForEpoch0 = await makeEpochProofQuote({ - epochToProve: 0n, - validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration + 10), - bondAmount: 10000n, - basisPointFee: 1, - signer: proverSigner, - }); - - await ctx.proverNode!.sendEpochProofQuote(quoteForEpoch0); - - // 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(); - - currentPending++; - - // Verify that we can claim the current epoch - await expectProofClaimOnL1({ ...quoteForEpoch0.payload, proposer: publisherAddress }); - await expectTips({ pending: currentPending, proven: currentProven }); - - // Now go to epoch 1 - await advanceToNextEpoch(); - - // now 'prove' epoch 0 - const epoch0BlockNumber = await getPendingBlockNumber(); - await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]); - - currentProven = epoch0BlockNumber; - - // Go to epoch 2 - await advanceToNextEpoch(); - - // Progress epochs with a block in each until we hit a reorg - // Note tips are block numbers, not slots - await expectTips({ pending: currentPending, proven: currentProven }); - const tx2BeforeReorg = await contract.methods.create_note(recipient, recipient, 10).send().wait(); - currentPending++; - await expectTips({ pending: currentPending, proven: currentProven }); - - // Go to epoch 3 - await advanceToNextEpoch(); - const tx3BeforeReorg = await contract.methods.create_note(recipient, recipient, 10).send().wait(); - currentPending++; - await expectTips({ pending: currentPending, proven: currentProven }); - - // Go to epoch 4 !!! REORG !!! ay caramba !!! - await advanceToNextEpoch(); - - // Wait a bit for the sequencer / node to notice a re-org - await sleep(2000); - await retryUntil( - async () => (await ctx.aztecNode.getTxReceipt(tx2BeforeReorg.txHash)).status === TxStatus.SUCCESS, - 'wait for re-inclusion', - 60, - 1, - ); - - // the sequencer will add valid txs again but in a new block - const tx2AfterReorg = await ctx.aztecNode.getTxReceipt(tx2BeforeReorg.txHash); - const tx3AfterReorg = await ctx.aztecNode.getTxReceipt(tx3BeforeReorg.txHash); - - // the tx from epoch 2 is still valid since it references a proven block - // this will be added back onto the chain - expect(tx2AfterReorg.status).toEqual(TxStatus.SUCCESS); - expect(tx2AfterReorg.blockNumber).toEqual(tx2BeforeReorg.blockNumber); - expect(tx2AfterReorg.blockHash).not.toEqual(tx2BeforeReorg.blockHash); - - // the tx from epoch 3 is not valid anymore, since it was built against a reorged block - // should be dropped - expect(tx3AfterReorg.status).toEqual(TxStatus.DROPPED); - - // new pxe, as it does not support reorgs - const pxeServiceConfig = { ...getPXEServiceConfig() }; - const newPxe = await createPXEService(ctx.aztecNode, pxeServiceConfig); - const newWallet = await createAccount(newPxe); - const newWalletAddress = newWallet.getAddress(); - - // after the re-org the pending chain has moved on by 2 blocks - currentPending = currentProven + 2n; - - // The chain will prune back to the proven block number - // then include the txs from the pruned epochs that are still valid - // bringing us back to block proven + 1 (same number, different hash) - // creating a new account will produce another block - // so we expect proven + 2 blocks in the pending chain here! - await expectTips({ pending: currentPending, proven: currentProven }); - - // Submit proof claim for the new epoch - const quoteForEpoch4 = await makeEpochProofQuote({ - epochToProve: 4n, - validUntilSlot: BigInt(ctx.aztecNodeConfig.aztecEpochDuration * 4 + 10), - bondAmount: 10000n, - basisPointFee: 1, - signer: proverSigner, - }); - await ctx.proverNode!.sendEpochProofQuote(quoteForEpoch4); - - logger.info(`Registering contract at ${contract.address} in new pxe`); - await newPxe.registerContract({ instance: contract.instance, artifact: StatefulTestContractArtifact }); - const contractFromNewPxe = await StatefulTestContract.at(contract.address, newWallet); - - logger.info('Sending new tx on reorged chain'); - await contractFromNewPxe.methods.create_note(newWalletAddress, newWalletAddress, 10).send().wait(); - currentPending++; - await expectTips({ pending: currentPending, proven: currentProven }); - - // Expect the proof claim to be accepted for the chain after the reorg - await expectProofClaimOnL1({ ...quoteForEpoch4.payload, proposer: publisherAddress }); - }); -}); diff --git a/yarn-project/ethereum/src/config.ts b/yarn-project/ethereum/src/config.ts index 7b411b1938aa..95ea3480b8cd 100644 --- a/yarn-project/ethereum/src/config.ts +++ b/yarn-project/ethereum/src/config.ts @@ -17,7 +17,7 @@ export type L1ContractsConfig = { /** The target validator committee size. */ aztecTargetCommitteeSize: number; /** The number of L2 slots that we can wait for a proof of an epoch to be produced. */ - aztecEpochProofClaimWindowInL2Slots: number; + aztecProofSubmissionWindow: number; /** The minimum stake for a validator. */ minimumStake: bigint; /** The slashing quorum */ @@ -35,7 +35,7 @@ export const DefaultL1ContractsConfig = { aztecSlotDuration: 24, aztecEpochDuration: 16, aztecTargetCommitteeSize: 48, - aztecEpochProofClaimWindowInL2Slots: 13, + aztecProofSubmissionWindow: 32, // you have a full epoch to submit a proof after the epoch to prove ends minimumStake: BigInt(100e18), slashingQuorum: 6, slashingRoundSize: 10, @@ -64,10 +64,11 @@ export const l1ContractsConfigMappings: ConfigMappingsType = description: 'The target validator committee size.', ...numberConfigHelper(DefaultL1ContractsConfig.aztecTargetCommitteeSize), }, - aztecEpochProofClaimWindowInL2Slots: { - env: 'AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS', - description: 'The number of L2 slots that we can wait for a proof of an epoch to be produced.', - ...numberConfigHelper(DefaultL1ContractsConfig.aztecEpochProofClaimWindowInL2Slots), + aztecProofSubmissionWindow: { + env: 'AZTEC_PROOF_SUBMISSION_WINDOW', + description: + 'The number of L2 slots that a proof for an epoch can be submitted in, starting from the beginning of the epoch.', + ...numberConfigHelper(DefaultL1ContractsConfig.aztecProofSubmissionWindow), }, minimumStake: { env: 'AZTEC_MINIMUM_STAKE', diff --git a/yarn-project/ethereum/src/contracts/rollup.ts b/yarn-project/ethereum/src/contracts/rollup.ts index 6fc021d316dc..1def33e1440f 100644 --- a/yarn-project/ethereum/src/contracts/rollup.ts +++ b/yarn-project/ethereum/src/contracts/rollup.ts @@ -34,14 +34,6 @@ export type L1RollupContractAddresses = Pick< | 'rewardDistributorAddress' >; -export type EpochProofQuoteViemArgs = { - epochToProve: bigint; - validUntilSlot: bigint; - bondAmount: bigint; - prover: `0x${string}`; - basisPointFee: number; -}; - export class RollupContract { private readonly rollup: GetContractReturnType>; @@ -97,8 +89,8 @@ export class RollupContract { } @memoize - getClaimDurationInL2Slots() { - return this.rollup.read.CLAIM_DURATION_IN_L2_SLOTS(); + getProofSubmissionWindow() { + return this.rollup.read.getProofSubmissionWindow(); } @memoize @@ -171,10 +163,6 @@ export class RollupContract { return this.rollup.read.getBlock([blockNumber]); } - getProofCommitmentEscrow() { - return this.rollup.read.PROOF_COMMITMENT_ESCROW(); - } - getTips() { return this.rollup.read.getTips(); } @@ -224,6 +212,7 @@ export class RollupContract { getEpochProofPublicInputs( args: readonly [ + bigint, bigint, readonly [ `0x${string}`, @@ -241,38 +230,6 @@ export class RollupContract { ) { return this.rollup.read.getEpochProofPublicInputs(args); } - public async getProofClaim() { - const { - epochToProve, - basisPointFee, - bondAmount, - bondProvider: bondProviderHex, - proposerClaimant: proposerClaimantHex, - } = await this.rollup.read.getProofClaim(); - - const bondProvider = EthAddress.fromString(bondProviderHex); - const proposerClaimant = EthAddress.fromString(proposerClaimantHex); - - if (bondProvider.isZero() && proposerClaimant.isZero() && epochToProve === 0n) { - return undefined; - } - - return { - epochToProve, - basisPointFee, - bondAmount, - bondProvider, - proposerClaimant, - }; - } - - async getClaimableEpoch(): Promise { - try { - return await this.rollup.read.getClaimableEpoch(); - } catch (err: unknown) { - throw formatViemError(err); - } - } public async getEpochToProve(): Promise { try { @@ -282,26 +239,6 @@ export class RollupContract { } } - public async validateProofQuote( - quote: { - quote: EpochProofQuoteViemArgs; - signature: ViemSignature; - }, - account: `0x${string}` | Account, - slotDuration: bigint | number, - ): Promise { - if (typeof slotDuration === 'number') { - slotDuration = BigInt(slotDuration); - } - const timeOfNextL1Slot = BigInt((await this.client.getBlock()).timestamp + slotDuration); - const args = [timeOfNextL1Slot, quote] as const; - try { - await this.rollup.read.validateEpochProofRightClaimAtTime(args, { account }); - } catch (err) { - throw formatViemError(err); - } - } - public async validateHeader( args: readonly [ `0x${string}`, diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index dbfdd45e05fa..0cb789969136 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -362,11 +362,12 @@ export const deployL1Contracts = async ( aztecSlotDuration: args.aztecSlotDuration, aztecEpochDuration: args.aztecEpochDuration, targetCommitteeSize: args.aztecTargetCommitteeSize, - aztecEpochProofClaimWindowInL2Slots: args.aztecEpochProofClaimWindowInL2Slots, + aztecProofSubmissionWindow: args.aztecProofSubmissionWindow, minimumStake: args.minimumStake, slashingQuorum: args.slashingQuorum, slashingRoundSize: args.slashingRoundSize, }; + logger.verbose(`Rollup config args`, rollupConfigArgs); const rollupArgs = [ feeJuicePortalAddress.toString(), rewardDistributorAddress.toString(), diff --git a/yarn-project/ethereum/src/queries.ts b/yarn-project/ethereum/src/queries.ts index 0b2ff75239a8..f65849aff370 100644 --- a/yarn-project/ethereum/src/queries.ts +++ b/yarn-project/ethereum/src/queries.ts @@ -39,7 +39,7 @@ export async function getL1ContractsConfig( l1StartBlock, l1GenesisTime, aztecEpochDuration, - aztecEpochProofClaimWindowInL2Slots, + aztecProofSubmissionWindow, aztecSlotDuration, aztecTargetCommitteeSize, minimumStake, @@ -51,7 +51,7 @@ export async function getL1ContractsConfig( rollup.getL1StartBlock(), rollup.getL1GenesisTime(), rollup.getEpochDuration(), - rollup.getClaimDurationInL2Slots(), + rollup.getProofSubmissionWindow(), rollup.getSlotDuration(), rollup.getTargetCommitteeSize(), rollup.getMinimumStake(), @@ -65,7 +65,7 @@ export async function getL1ContractsConfig( l1StartBlock, l1GenesisTime, aztecEpochDuration: Number(aztecEpochDuration), - aztecEpochProofClaimWindowInL2Slots: Number(aztecEpochProofClaimWindowInL2Slots), + aztecProofSubmissionWindow: Number(aztecProofSubmissionWindow), aztecSlotDuration: Number(aztecSlotDuration), aztecTargetCommitteeSize: Number(aztecTargetCommitteeSize), governanceProposerQuorum: Number(governanceProposerQuorum), diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 9252e16bacc7..e71cf9a96e0c 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -144,9 +144,6 @@ export type EnvVar = | 'PROVER_TEST_DELAY_MS' | 'PXE_L2_STARTING_BLOCK' | 'PXE_PROVER_ENABLED' - | 'QUOTE_PROVIDER_BASIS_POINT_FEE' - | 'QUOTE_PROVIDER_BOND_AMOUNT' - | 'QUOTE_PROVIDER_URL' | 'PROVER_TARGET_ESCROW_AMOUNT' | 'PROVER_MINIMUM_ESCROW_AMOUNT' | 'REGISTRY_CONTRACT_ADDRESS' @@ -189,7 +186,7 @@ export type EnvVar = | 'AZTEC_SLOT_DURATION' | 'AZTEC_EPOCH_DURATION' | 'AZTEC_TARGET_COMMITTEE_SIZE' - | 'AZTEC_EPOCH_PROOF_CLAIM_WINDOW_IN_L2_SLOTS' + | 'AZTEC_PROOF_SUBMISSION_WINDOW' | 'AZTEC_MINIMUM_STAKE' | 'AZTEC_SLASHING_QUORUM' | 'AZTEC_SLASHING_ROUND_SIZE' diff --git a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh index 66ea40475eed..989e438787ee 100755 --- a/yarn-project/l1-artifacts/scripts/generate-artifacts.sh +++ b/yarn-project/l1-artifacts/scripts/generate-artifacts.sh @@ -22,8 +22,6 @@ contracts=( "FeeJuicePortal" "MockVerifier" "IVerifier" - "IProofCommitmentEscrow" - "ProofCommitmentEscrow" "CoinIssuer" "RewardDistributor" "GovernanceProposer" @@ -128,7 +126,7 @@ done # Update index.ts exports echo "export * from './RollupStorage.js';" >>"generated/index.ts" -# Write abis hash. Consider excluding some contracts from this hash if +# Write abis hash. Consider excluding some contracts from this hash if # we don't want to consider them as breaking for the interfaces. echo "export const AbisChecksum = \"$(echo -n "$abis" | sha256sum | cut -d' ' -f1)\";" >"generated/checksum.ts" echo "export * from './checksum.js';" >>"generated/index.ts" diff --git a/yarn-project/p2p/src/client/factory.ts b/yarn-project/p2p/src/client/factory.ts index 9b0541349621..f7b82ac2cf4f 100644 --- a/yarn-project/p2p/src/client/factory.ts +++ b/yarn-project/p2p/src/client/factory.ts @@ -15,8 +15,6 @@ import { P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; import { InMemoryAttestationPool } from '../mem_pools/attestation_pool/memory_attestation_pool.js'; -import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; -import { MemoryEpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js'; import { type MemPools } from '../mem_pools/interface.js'; import { AztecKVTxPool, type TxPool } from '../mem_pools/tx_pool/index.js'; import { DiscV5Service } from '../services/discv5/discV5_service.js'; @@ -28,7 +26,6 @@ type P2PClientDeps = { txPool?: TxPool; store?: AztecAsyncKVStore; attestationPool?: T extends P2PClientType.Full ? AttestationPool : undefined; - epochProofQuotePool?: EpochProofQuotePool; logger?: Logger; }; @@ -49,7 +46,6 @@ export const createP2PClient = async ( const mempools: MemPools = { txPool: deps.txPool ?? new AztecKVTxPool(store, archive, telemetry, config.archivedTxLimit), - epochProofQuotePool: deps.epochProofQuotePool ?? new MemoryEpochProofQuotePool(telemetry), attestationPool: clientType === P2PClientType.Full ? ((deps.attestationPool ?? new InMemoryAttestationPool(telemetry)) as T extends P2PClientType.Full diff --git a/yarn-project/p2p/src/client/p2p_client.integration.test.ts b/yarn-project/p2p/src/client/p2p_client.integration.test.ts index d2eb6f39efe1..dbbdba64844b 100644 --- a/yarn-project/p2p/src/client/p2p_client.integration.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.integration.test.ts @@ -10,7 +10,6 @@ import { type MockProxy, mock } from 'jest-mock-extended'; import { type P2PClient } from '../client/p2p_client.js'; import { type P2PConfig, getP2PDefaultConfig } from '../config.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; -import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type TxPool } from '../mem_pools/tx_pool/index.js'; import { makeTestP2PClients } from '../test-helpers/make-test-p2p-clients.js'; @@ -21,7 +20,6 @@ const NUMBER_OF_PEERS = 2; describe('p2p client integration', () => { let txPool: MockProxy; let attestationPool: MockProxy; - let epochProofQuotePool: MockProxy; let epochCache: MockProxy; let worldState: MockProxy; @@ -32,7 +30,6 @@ describe('p2p client integration', () => { beforeEach(() => { txPool = mock(); attestationPool = mock(); - epochProofQuotePool = mock(); epochCache = mock(); worldState = mock(); @@ -65,7 +62,6 @@ describe('p2p client integration', () => { clients = await makeTestP2PClients(NUMBER_OF_PEERS, { p2pBaseConfig: { ...emptyChainConfig, ...getP2PDefaultConfig() }, mockAttestationPool: attestationPool, - mockEpochProofQuotePool: epochProofQuotePool, mockTxPool: txPool, mockEpochCache: epochCache, mockWorldState: worldState, @@ -94,7 +90,6 @@ describe('p2p client integration', () => { clients = await makeTestP2PClients(NUMBER_OF_PEERS, { p2pBaseConfig, mockAttestationPool: attestationPool, - mockEpochProofQuotePool: epochProofQuotePool, mockTxPool: txPool, mockEpochCache: epochCache, mockWorldState: worldState, @@ -127,7 +122,6 @@ describe('p2p client integration', () => { clients = await makeTestP2PClients(NUMBER_OF_PEERS, { p2pBaseConfig, mockAttestationPool: attestationPool, - mockEpochProofQuotePool: epochProofQuotePool, mockTxPool: txPool, mockEpochCache: epochCache, mockWorldState: worldState, @@ -167,7 +161,6 @@ describe('p2p client integration', () => { clients = await makeTestP2PClients(NUMBER_OF_PEERS, { p2pBaseConfig, mockAttestationPool: attestationPool, - mockEpochProofQuotePool: epochProofQuotePool, mockTxPool: txPool, mockEpochCache: epochCache, mockWorldState: worldState, diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index b68d7e6ce522..26e753cacf74 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -1,5 +1,5 @@ import { MockL2BlockSource } from '@aztec/archiver/test'; -import { L2Block, P2PClientType, mockEpochProofQuote, mockTx } from '@aztec/circuit-types'; +import { L2Block, P2PClientType, mockTx } from '@aztec/circuit-types'; import { Fr } from '@aztec/circuits.js'; import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; @@ -9,7 +9,7 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { expect } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { type EpochProofQuotePool, type P2PService } from '../index.js'; +import { type P2PService } from '../index.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; import { type MemPools } from '../mem_pools/interface.js'; import { type TxPool } from '../mem_pools/tx_pool/index.js'; @@ -18,7 +18,6 @@ import { P2PClient } from './p2p_client.js'; describe('In-Memory P2P Client', () => { let txPool: MockProxy; let attestationPool: MockProxy; - let epochProofQuotePool: MockProxy; let mempools: MemPools; let blockSource: MockL2BlockSource; let p2pService: MockProxy; @@ -36,16 +35,12 @@ describe('In-Memory P2P Client', () => { attestationPool = mock(); - epochProofQuotePool = mock(); - epochProofQuotePool.getQuotes.mockReturnValue([]); - blockSource = new MockL2BlockSource(); await blockSource.createBlocks(100); mempools = { txPool, attestationPool, - epochProofQuotePool, }; kvStore = await openTmpStore('test'); @@ -141,65 +136,6 @@ describe('In-Memory P2P Client', () => { await client.stop(); }); - it('stores and returns epoch proof quotes', async () => { - client = new P2PClient(P2PClientType.Full, kvStore, blockSource, mempools, p2pService); - - 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) { - await client.addEpochProofQuote(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); - }); - - // TODO(#10737) flake cc Maddiaa0 - it.skip('deletes expired proof quotes', async () => { - client = new P2PClient(P2PClientType.Full, kvStore, blockSource, mempools, p2pService); - - 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); - } - - epochProofQuotePool.deleteQuotesToEpoch.mockReset(); - - await advanceToProvenBlock(3, 3); - - expect(epochProofQuotePool.deleteQuotesToEpoch).toBeCalledWith(3n); - }); - describe('Chain prunes', () => { it('moves the tips on a chain reorg', async () => { blockSource.setProvenBlockNumber(0); diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index cc4a76494c4d..e87b09732fda 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -1,7 +1,6 @@ import { type BlockAttestation, type BlockProposal, - type EpochProofQuote, type L2Block, type L2BlockId, type L2BlockSource, @@ -30,7 +29,6 @@ import { type ENR } from '@chainsafe/enr'; import { type P2PConfig, getP2PDefaultConfig } from '../config.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; -import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type MemPools } from '../mem_pools/interface.js'; import { type TxPool } from '../mem_pools/tx_pool/index.js'; import { ReqRespSubProtocol } from '../services/reqresp/interface.js'; @@ -72,21 +70,6 @@ export type P2P = ProverCoordinati */ broadcastProposal(proposal: BlockProposal): void; - /** - * Queries the EpochProofQuote pool for quotes for the given epoch - * - * @param epoch - the epoch to query - * @returns EpochProofQuotes - */ - getEpochProofQuotes(epoch: bigint): Promise; - - /** - * Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers. - * - * @param quote - the quote to broadcast - */ - addEpochProofQuote(quote: EpochProofQuote): Promise; - /** * Registers a callback from the validator client that determines how to behave when * foreign block proposals are received @@ -211,7 +194,6 @@ export class P2PClient private txPool: TxPool; private attestationPool: T extends P2PClientType.Full ? AttestationPool : undefined; - private epochProofQuotePool: EpochProofQuotePool; /** How many slots to keep attestations for. */ private keepAttestationsInPoolFor: number; @@ -260,7 +242,6 @@ export class P2PClient this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block'); this.txPool = mempools.txPool; - this.epochProofQuotePool = mempools.epochProofQuotePool; this.attestationPool = mempools.attestationPool!; } @@ -337,26 +318,6 @@ export class P2PClient } } - /** - * Adds an EpochProofQuote to the pool and broadcasts an EpochProofQuote to other peers. - * @param quote - the quote to broadcast - */ - addEpochProofQuote(quote: EpochProofQuote): Promise { - this.epochProofQuotePool.addQuote(quote); - this.broadcastEpochProofQuote(quote); - return Promise.resolve(); - } - - getEpochProofQuotes(epoch: bigint): Promise { - return Promise.resolve(this.epochProofQuotePool.getQuotes(epoch)); - } - - broadcastEpochProofQuote(quote: EpochProofQuote): void { - this.#assertIsReady(); - this.log.info('Broadcasting epoch proof quote', quote.toViemArgs()); - return this.p2pService.propagate(quote); - } - /** * Starts the P2P client. * @returns An empty promise signalling the synching process. @@ -731,11 +692,6 @@ export class P2PClient await this.attestationPool?.deleteAttestationsOlderThan(lastBlockSlotMinusKeepAttestationsInPoolFor); } - const provenEpochNumber = await this.l2BlockSource.getProvenL2EpochNumber(); - if (provenEpochNumber !== undefined) { - this.epochProofQuotePool.deleteQuotesToEpoch(BigInt(provenEpochNumber)); - } - await this.synchedProvenBlockNumber.set(lastBlockNum); this.log.debug(`Synched to proven block ${lastBlockNum}`); diff --git a/yarn-project/p2p/src/index.ts b/yarn-project/p2p/src/index.ts index f6f67f3afdb6..da41de922c9b 100644 --- a/yarn-project/p2p/src/index.ts +++ b/yarn-project/p2p/src/index.ts @@ -1,8 +1,7 @@ -export * from './mem_pools/attestation_pool/index.js'; export * from './bootstrap/bootstrap.js'; export * from './client/index.js'; export * from './config.js'; -export * from './mem_pools/epoch_proof_quote_pool/index.js'; -export * from './services/index.js'; +export * from './mem_pools/attestation_pool/index.js'; export * from './mem_pools/tx_pool/index.js'; export * from './msg_validators/index.js'; +export * from './services/index.js'; diff --git a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.ts deleted file mode 100644 index 94776d04ac33..000000000000 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.ts +++ /dev/null @@ -1,7 +0,0 @@ -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/mem_pools/epoch_proof_quote_pool/index.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/index.ts deleted file mode 100644 index 8073ff1866f0..000000000000 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -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/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts deleted file mode 100644 index 2f5d82670585..000000000000 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types'; - -import { type MockProxy, mock } from 'jest-mock-extended'; - -import { type PoolInstrumentation } from '../instrumentation.js'; -import { MemoryEpochProofQuotePool } from './memory_epoch_proof_quote_pool.js'; - -describe('MemoryEpochProofQuotePool', () => { - let pool: MemoryEpochProofQuotePool; - - let metricsMock: MockProxy>; - - beforeEach(() => { - pool = new MemoryEpochProofQuotePool(); - - metricsMock = mock>(); - (pool as any).metrics = metricsMock; - }); - - it('should add/get quotes to/from pool', () => { - const quote = mockEpochProofQuote(5n); - - pool.addQuote(quote); - - expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(1); - - const quotes = pool.getQuotes(quote.payload.epochToProve); - - 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); - const quotesForEpoch2 = proofQuotes.filter(x => x.payload.epochToProve === 2n); - - expect(quotes3).toHaveLength(quotesForEpoch3.length); - expect(quotes3).toEqual(quotesForEpoch3); - - // should delete all quotes for epochs 2 and 3 - pool.deleteQuotesToEpoch(3n); - - expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(quotesForEpoch2.length + quotesForEpoch3.length); - - 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/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts deleted file mode 100644 index 0896787892cb..000000000000 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { type EpochProofQuote } from '@aztec/circuit-types'; -import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; - -import { PoolInstrumentation, PoolName } from '../instrumentation.js'; -import { type EpochProofQuotePool } from './epoch_proof_quote_pool.js'; - -export class MemoryEpochProofQuotePool implements EpochProofQuotePool { - private quotes: Map; - private metrics: PoolInstrumentation; - - constructor(telemetry: TelemetryClient = getTelemetryClient()) { - this.quotes = new Map(); - this.metrics = new PoolInstrumentation(telemetry, PoolName.EPOCH_PROOF_QUOTE_POOL); - } - - addQuote(quote: EpochProofQuote) { - const epoch = quote.payload.epochToProve; - if (!this.quotes.has(epoch)) { - this.quotes.set(epoch, []); - } - this.quotes.get(epoch)!.push(quote); - - this.metrics.recordAddedObjects(1); - } - getQuotes(epoch: bigint): EpochProofQuote[] { - return this.quotes.get(epoch) || []; - } - deleteQuotesToEpoch(epoch: bigint): void { - const expiredEpochs = Array.from(this.quotes.keys()).filter(k => k <= epoch); - - let removedObjectsCount = 0; - for (const expiredEpoch of expiredEpochs) { - // For logging - removedObjectsCount += this.quotes.get(expiredEpoch)?.length || 0; - - this.quotes.delete(expiredEpoch); - } - - this.metrics.recordRemovedObjects(removedObjectsCount); - } -} diff --git a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts b/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts deleted file mode 100644 index a59de8daa3c0..000000000000 --- a/yarn-project/p2p/src/mem_pools/epoch_proof_quote_pool/test_utils.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types'; -import { EthAddress } from '@aztec/circuits.js'; -import { Buffer32 } from '@aztec/foundation/buffer'; -import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto'; - -export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload { - return EpochProofQuotePayload.from({ - basisPointFee: randomInt(10000), - bondAmount: 1000000000000000000n, - epochToProve: randomBigInt(1000000n), - prover: EthAddress.random(), - validUntilSlot: randomBigInt(1000000n), - }); -} - -export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): { - quote: EpochProofQuote; - signer: Secp256k1Signer; -} { - const signer = Secp256k1Signer.random(); - - return { - quote: EpochProofQuote.new(Buffer32.random(), payload ?? makeRandomEpochProofQuotePayload(), signer), - signer, - }; -} diff --git a/yarn-project/p2p/src/mem_pools/index.ts b/yarn-project/p2p/src/mem_pools/index.ts index 683ac12e4599..3444bc7f9485 100644 --- a/yarn-project/p2p/src/mem_pools/index.ts +++ b/yarn-project/p2p/src/mem_pools/index.ts @@ -1,4 +1,3 @@ -export { TxPool } from './tx_pool/tx_pool.js'; export { AttestationPool } from './attestation_pool/attestation_pool.js'; -export { EpochProofQuotePool } from './epoch_proof_quote_pool/epoch_proof_quote_pool.js'; export { type MemPools } from './interface.js'; +export { TxPool } from './tx_pool/tx_pool.js'; diff --git a/yarn-project/p2p/src/mem_pools/instrumentation.ts b/yarn-project/p2p/src/mem_pools/instrumentation.ts index 431a3e95c896..1105d6a37a6e 100644 --- a/yarn-project/p2p/src/mem_pools/instrumentation.ts +++ b/yarn-project/p2p/src/mem_pools/instrumentation.ts @@ -12,7 +12,6 @@ import { export enum PoolName { TX_POOL = 'TxPool', ATTESTATION_POOL = 'AttestationPool', - EPOCH_PROOF_QUOTE_POOL = 'EpochProofQuotePool', } type MetricsLabels = { @@ -36,11 +35,6 @@ function getMetricsLabels(name: PoolName): MetricsLabels { objectInMempool: Metrics.MEMPOOL_ATTESTATIONS_COUNT, objectSize: Metrics.MEMPOOL_ATTESTATIONS_SIZE, }; - } else if (name === PoolName.EPOCH_PROOF_QUOTE_POOL) { - return { - objectInMempool: Metrics.MEMPOOL_PROVER_QUOTE_COUNT, - objectSize: Metrics.MEMPOOL_PROVER_QUOTE_SIZE, - }; } throw new Error('Invalid pool type'); diff --git a/yarn-project/p2p/src/mem_pools/interface.ts b/yarn-project/p2p/src/mem_pools/interface.ts index 4db4c0fa8990..fa74e4f17138 100644 --- a/yarn-project/p2p/src/mem_pools/interface.ts +++ b/yarn-project/p2p/src/mem_pools/interface.ts @@ -1,7 +1,6 @@ import { type P2PClientType } from '@aztec/circuit-types'; import { type AttestationPool } from './attestation_pool/attestation_pool.js'; -import { type EpochProofQuotePool } from './epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type TxPool } from './tx_pool/tx_pool.js'; /** @@ -10,5 +9,4 @@ import { type TxPool } from './tx_pool/tx_pool.js'; export type MemPools = { txPool: TxPool; attestationPool?: T extends P2PClientType.Full ? AttestationPool : undefined; - epochProofQuotePool: EpochProofQuotePool; }; diff --git a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts deleted file mode 100644 index de90b81f9c30..000000000000 --- a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { EpochProofQuote, EpochProofQuotePayload, PeerErrorSeverity } from '@aztec/circuit-types'; -import { type EpochCache } from '@aztec/epoch-cache'; -import { EthAddress } from '@aztec/foundation/eth-address'; -import { Signature } from '@aztec/foundation/eth-signature'; - -import { mock } from 'jest-mock-extended'; - -import { EpochProofQuoteValidator } from './epoch_proof_quote_validator.js'; - -describe('EpochProofQuoteValidator', () => { - let epochCache: EpochCache; - let validator: EpochProofQuoteValidator; - - beforeEach(() => { - epochCache = mock(); - validator = new EpochProofQuoteValidator(epochCache); - }); - - const makeEpochProofQuote = (epochToProve: bigint) => { - const payload = EpochProofQuotePayload.from({ - basisPointFee: 5000, - bondAmount: 1000000000000000000n, - epochToProve, - prover: EthAddress.random(), - validUntilSlot: 100n, - }); - return new EpochProofQuote(payload, Signature.random()); - }; - - it('returns high tolerance error if epoch to prove is not current or previous epoch', async () => { - // Create an epoch proof quote for epoch 5 - const mockQuote = makeEpochProofQuote(5n); - - // Mock epoch cache to return different epoch - (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ - epoch: 7n, - }); - - const result = await validator.validate(mockQuote); - expect(result).toBe(PeerErrorSeverity.HighToleranceError); - }); - - it('returns no error if epoch to prove is current epoch', async () => { - // Create an epoch proof quote for current epoch - const mockQuote = makeEpochProofQuote(7n); - - // Mock epoch cache to return matching epoch - (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ - epoch: 7n, - }); - - const result = await validator.validate(mockQuote); - expect(result).toBeUndefined(); - }); - - it('returns no error if epoch to prove is previous epoch', async () => { - // Create an epoch proof quote for previous epoch - const mockQuote = makeEpochProofQuote(6n); - - // Mock epoch cache to return current epoch - (epochCache.getEpochAndSlotNow as jest.Mock).mockReturnValue({ - epoch: 7n, - }); - - const result = await validator.validate(mockQuote); - expect(result).toBeUndefined(); - }); -}); diff --git a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts deleted file mode 100644 index 826334d18eb3..000000000000 --- a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/epoch_proof_quote_validator.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { type EpochProofQuote, type P2PValidator, PeerErrorSeverity } from '@aztec/circuit-types'; -import { type EpochCacheInterface } from '@aztec/epoch-cache'; - -export class EpochProofQuoteValidator implements P2PValidator { - private epochCache: EpochCacheInterface; - - constructor(epochCache: EpochCacheInterface) { - this.epochCache = epochCache; - } - - validate(message: EpochProofQuote): Promise { - const { epoch } = this.epochCache.getEpochAndSlotNow(); - - // Check that the epoch proof quote is for the current epoch - const epochToProve = message.payload.epochToProve; - if (epochToProve !== epoch && epochToProve !== epoch - 1n) { - return Promise.resolve(PeerErrorSeverity.HighToleranceError); - } - - return Promise.resolve(undefined); - } -} diff --git a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/index.ts b/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/index.ts deleted file mode 100644 index fc8ab006e7e1..000000000000 --- a/yarn-project/p2p/src/msg_validators/epoch_proof_quote_validator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { EpochProofQuoteValidator } from './epoch_proof_quote_validator.js'; diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 750dbd31f644..d679b2a989d7 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -2,7 +2,6 @@ import { BlockAttestation, BlockProposal, type ClientProtocolCircuitVerifier, - EpochProofQuote, type Gossipable, type L2BlockSource, MerkleTreeId, @@ -48,7 +47,6 @@ import { createLibp2p } from 'libp2p'; import { type P2PConfig } from '../../config.js'; import { type MemPools } from '../../mem_pools/interface.js'; -import { EpochProofQuoteValidator } from '../../msg_validators/epoch_proof_quote_validator/index.js'; import { AttestationValidator, BlockProposalValidator } from '../../msg_validators/index.js'; import { DataTxValidator, @@ -95,7 +93,6 @@ export class LibP2PService extends WithTracer implement // Message validators private attestationValidator: AttestationValidator; private blockProposalValidator: BlockProposalValidator; - private epochProofQuoteValidator: EpochProofQuoteValidator; // Request and response sub service public reqresp: ReqResp; @@ -143,7 +140,6 @@ export class LibP2PService extends WithTracer implement this.attestationValidator = new AttestationValidator(epochCache); this.blockProposalValidator = new BlockProposalValidator(epochCache); - this.epochProofQuoteValidator = new EpochProofQuoteValidator(epochCache); this.blockReceivedCallback = async (block: BlockProposal): Promise => { this.logger.warn( @@ -265,11 +261,6 @@ export class LibP2PService extends WithTracer implement invalidMessageDeliveriesWeight: -20, invalidMessageDeliveriesDecay: 0.5, }), - [EpochProofQuote.p2pTopic]: createTopicScoreParams({ - topicWeight: 1, - invalidMessageDeliveriesWeight: -20, - invalidMessageDeliveriesDecay: 0.5, - }), }, }), }) as (components: GossipSubComponents) => GossipSub, @@ -343,7 +334,6 @@ export class LibP2PService extends WithTracer implement [Tx.p2pTopic]: this.validatePropagatedTxFromMessage.bind(this), [BlockAttestation.p2pTopic]: this.validatePropagatedAttestationFromMessage.bind(this), [BlockProposal.p2pTopic]: this.validatePropagatedBlockFromMessage.bind(this), - [EpochProofQuote.p2pTopic]: this.validatePropagatedEpochProofQuoteFromMessage.bind(this), }; // When running bandwidth benchmarks, we use send blobs of data we do not want to validate // NEVER switch this off in production @@ -507,10 +497,6 @@ export class LibP2PService extends WithTracer implement const block = BlockProposal.fromBuffer(Buffer.from(message.data)); await this.processBlockFromPeer(block); } - if (message.topic == EpochProofQuote.p2pTopic) { - const epochProofQuote = EpochProofQuote.fromBuffer(Buffer.from(message.data)); - await this.processEpochProofQuoteFromPeer(epochProofQuote); - } return; } @@ -594,17 +580,6 @@ export class LibP2PService extends WithTracer implement await this.propagate(attestation); } - private async processEpochProofQuoteFromPeer(epochProofQuote: EpochProofQuote) { - const epoch = epochProofQuote.payload.epochToProve; - const prover = epochProofQuote.payload.prover.toString(); - const p2pMessageIdentifier = await epochProofQuote.p2pMessageIdentifier(); - this.logger.verbose( - `Received epoch proof quote ${p2pMessageIdentifier} by prover ${prover} for epoch ${epoch} from external peer.`, - { quote: epochProofQuote.toInspect(), p2pMessageIdentifier }, - ); - this.mempools.epochProofQuotePool.addQuote(epochProofQuote); - } - /** * Propagates provided message to peers. * @param message - The message to propagate. @@ -718,25 +693,6 @@ export class LibP2PService extends WithTracer implement return isValid ? TopicValidatorResult.Accept : TopicValidatorResult.Reject; } - /** - * Validate an epoch proof quote from a peer. - * @param propagationSource - The peer ID of the peer that sent the epoch proof quote. - * @param msg - The epoch proof quote message. - * @returns True if the epoch proof quote is valid, false otherwise. - */ - private async validatePropagatedEpochProofQuoteFromMessage( - propagationSource: PeerId, - msg: Message, - ): Promise { - const epochProofQuote = EpochProofQuote.fromBuffer(Buffer.from(msg.data)); - const isValid = await this.validateEpochProofQuote(propagationSource, epochProofQuote); - this.logger.trace(`validatePropagatedEpochProofQuote: ${isValid}`, { - [Attributes.EPOCH_NUMBER]: epochProofQuote.payload.epochToProve.toString(), - [Attributes.P2P_ID]: propagationSource.toString(), - }); - return isValid ? TopicValidatorResult.Accept : TopicValidatorResult.Reject; - } - @trackSpan('Libp2pService.validatePropagatedTx', async tx => ({ [Attributes.TX_HASH]: (await tx.getTxHash()).toString(), })) @@ -908,25 +864,6 @@ export class LibP2PService extends WithTracer implement return true; } - /** - * Validate an epoch proof quote. - * - * @param epochProofQuote - The epoch proof quote to validate. - * @returns True if the epoch proof quote is valid, false otherwise. - */ - @trackSpan('Libp2pService.validateEpochProofQuote', (_peerId, epochProofQuote) => ({ - [Attributes.EPOCH_NUMBER]: epochProofQuote.payload.epochToProve.toString(), - })) - public async validateEpochProofQuote(peerId: PeerId, epochProofQuote: EpochProofQuote): Promise { - const severity = await this.epochProofQuoteValidator.validate(epochProofQuote); - if (severity) { - this.peerManager.penalizePeer(peerId, severity); - return false; - } - - return true; - } - public getPeerScore(peerId: PeerId): number { return this.node.services.pubsub.score.score(peerId.toString()); } diff --git a/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts b/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts index 356575862e55..cce4f99be305 100644 --- a/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts +++ b/yarn-project/p2p/src/test-helpers/make-test-p2p-clients.ts @@ -9,7 +9,6 @@ import { createP2PClient } from '../client/index.js'; import { type P2PClient } from '../client/p2p_client.js'; import { type P2PConfig } from '../config.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; -import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type TxPool } from '../mem_pools/tx_pool/index.js'; import { generatePeerIdPrivateKeys } from '../test-helpers/generate-peer-id-private-keys.js'; import { getPorts } from './get-ports.js'; @@ -18,7 +17,6 @@ import { AlwaysFalseCircuitVerifier, AlwaysTrueCircuitVerifier } from './reqresp interface MakeTestP2PClientOptions { mockAttestationPool: AttestationPool; - mockEpochProofQuotePool: EpochProofQuotePool; mockTxPool: TxPool; mockEpochCache: EpochCache; mockWorldState: WorldStateSynchronizer; @@ -45,7 +43,6 @@ export async function makeTestP2PClient( p2pBaseConfig, p2pConfigOverrides = {}, mockAttestationPool, - mockEpochProofQuotePool, mockTxPool, mockEpochCache, mockWorldState, @@ -79,7 +76,6 @@ export async function makeTestP2PClient( const deps = { txPool: mockTxPool as unknown as TxPool, attestationPool: mockAttestationPool as unknown as AttestationPool, - epochProofQuotePool: mockEpochProofQuotePool as unknown as EpochProofQuotePool, store: kvStore, logger, }; diff --git a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts index 328b595b67a2..53827c90696a 100644 --- a/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts +++ b/yarn-project/p2p/src/testbench/p2p_client_testbench_worker.ts @@ -15,7 +15,6 @@ import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; import { type P2PConfig } from '../config.js'; import { createP2PClient } from '../index.js'; import { type AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; -import { type EpochProofQuotePool } from '../mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js'; import { type TxPool } from '../mem_pools/tx_pool/index.js'; import { AlwaysTrueCircuitVerifier } from '../test-helpers/reqresp-nodes.js'; @@ -48,14 +47,6 @@ function mockAttestationPool(): AttestationPool { }; } -function mockEpochProofQuotePool(): EpochProofQuotePool { - return { - addQuote: () => {}, - getQuotes: () => [], - deleteQuotesToEpoch: () => {}, - }; -} - function mockEpochCache(): EpochCacheInterface { return { getCommittee: () => Promise.resolve([] as EthAddress[]), @@ -81,7 +72,6 @@ process.on('message', async msg => { if (type === 'START') { const txPool = mockTxPool(); const attestationPool = mockAttestationPool(); - const epochProofQuotePool = mockEpochProofQuotePool(); const epochCache = mockEpochCache(); const worldState = {} as WorldStateSynchronizer; const l2BlockSource = new MockL2BlockSource(); @@ -94,7 +84,6 @@ process.on('message', async msg => { const deps = { txPool, attestationPool, - epochProofQuotePool, store: kvStore, logger, }; diff --git a/yarn-project/prover-node/src/bond/bond-manager.test.ts b/yarn-project/prover-node/src/bond/bond-manager.test.ts deleted file mode 100644 index e2d6b14eb6d1..000000000000 --- a/yarn-project/prover-node/src/bond/bond-manager.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { EthAddress } from '@aztec/circuits.js'; - -import { type MockProxy, mock } from 'jest-mock-extended'; - -import { BondManager } from '../bond/bond-manager.js'; -import { type EscrowContract } from '../bond/escrow-contract.js'; -import { type TokenContract } from '../bond/token-contract.js'; - -describe('BondManager', () => { - let bondManager: BondManager; - - let tokenContract: MockProxy; - let escrowContract: MockProxy; - - beforeEach(() => { - tokenContract = mock(); - escrowContract = mock(); - - escrowContract.getEscrowAddress.mockReturnValue(EthAddress.random()); - tokenContract.getBalance.mockResolvedValue(10000n); - bondManager = new BondManager(tokenContract, escrowContract, 100n, 1000n); - }); - - it('ensures bond if current bond is below minimum', async () => { - escrowContract.getProverDeposit.mockResolvedValue(50n); - - await bondManager.ensureBond(); - - expect(escrowContract.getProverDeposit).toHaveBeenCalled(); - expect(tokenContract.ensureAllowance).toHaveBeenCalledWith(escrowContract.getEscrowAddress()); - expect(escrowContract.depositProverBond).toHaveBeenCalledWith(950n); - }); - - it('does not ensure bond if current bond is above minimum', async () => { - escrowContract.getProverDeposit.mockResolvedValue(200n); - - await bondManager.ensureBond(); - - expect(escrowContract.getProverDeposit).toHaveBeenCalled(); - expect(tokenContract.ensureAllowance).not.toHaveBeenCalled(); - expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); - }); - - it('throws error if not enough balance to top-up bond', async () => { - escrowContract.getProverDeposit.mockResolvedValue(50n); - tokenContract.getBalance.mockResolvedValue(100n); - - await expect(bondManager.ensureBond()).rejects.toThrow(/Not enough balance/i); - }); - - it('overrides minimum bond threshold', async () => { - escrowContract.getProverDeposit.mockResolvedValue(150n); - - await bondManager.ensureBond(); - expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); - - await bondManager.ensureBond(200n); - expect(escrowContract.getProverDeposit).toHaveBeenCalled(); - expect(tokenContract.ensureAllowance).toHaveBeenCalled(); - expect(escrowContract.depositProverBond).toHaveBeenCalledWith(850n); - }); - - it('overrides target bond threshold', async () => { - escrowContract.getProverDeposit.mockResolvedValue(150n); - - await bondManager.ensureBond(); - expect(escrowContract.depositProverBond).not.toHaveBeenCalled(); - - await bondManager.ensureBond(2000n); - expect(escrowContract.getProverDeposit).toHaveBeenCalled(); - expect(tokenContract.ensureAllowance).toHaveBeenCalled(); - expect(escrowContract.depositProverBond).toHaveBeenCalledWith(1850n); - }); -}); diff --git a/yarn-project/prover-node/src/bond/bond-manager.ts b/yarn-project/prover-node/src/bond/bond-manager.ts deleted file mode 100644 index ca9067ea1938..000000000000 --- a/yarn-project/prover-node/src/bond/bond-manager.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { createLogger } from '@aztec/foundation/log'; - -import { type EscrowContract } from './escrow-contract.js'; -import { type TokenContract } from './token-contract.js'; - -export class BondManager { - private readonly logger = createLogger('prover-node:bond-manager'); - - constructor( - private readonly tokenContract: TokenContract, - private readonly escrowContract: EscrowContract, - /** Minimum escrowed bond. A top-up will be issued once this threshold is hit. */ - public minimumAmount: bigint, - /** Target escrowed bond. Top-up will target this value. */ - public targetAmount: bigint, - ) {} - - /** - * Ensures the bond is at least minimumBond, or sends a tx to deposit the remaining to reach targetBond. - * @param overrideMinimum - Override the minimum bond threshold. Also overrides target if it is higher. - */ - public async ensureBond(overrideMinimum?: bigint) { - const minimum = overrideMinimum ?? this.minimumAmount; - const target = overrideMinimum && overrideMinimum > this.targetAmount ? overrideMinimum : this.targetAmount; - - try { - const current = await this.escrowContract.getProverDeposit(); - if (current >= minimum) { - this.logger.debug(`Current prover bond ${current} is above minimum ${minimum}`); - return; - } - - const topUpAmount = target - current; - this.logger.verbose(`Prover bond top-up ${topUpAmount} required to get ${current} to target ${target}`); - - const balance = await this.tokenContract.getBalance(); - if (balance < topUpAmount) { - throw new Error(`Not enough balance to top-up prover bond: ${balance} < ${topUpAmount}`); - } - - await this.tokenContract.ensureAllowance(this.escrowContract.getEscrowAddress()); - await this.escrowContract.depositProverBond(topUpAmount); - this.logger.verbose(`Prover bond top-up of ${topUpAmount} completed`); - } catch (err) { - throw new Error(`Could not set prover bond: ${err}`); - } - } -} diff --git a/yarn-project/prover-node/src/bond/config.ts b/yarn-project/prover-node/src/bond/config.ts deleted file mode 100644 index 70f2691a3d35..000000000000 --- a/yarn-project/prover-node/src/bond/config.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { type ConfigMappingsType, bigintConfigHelper, getConfigFromMappings } from '@aztec/foundation/config'; - -export type ProverBondManagerConfig = { - proverMinimumEscrowAmount: bigint; - proverTargetEscrowAmount?: bigint; -}; - -export const proverBondManagerConfigMappings: ConfigMappingsType = { - proverMinimumEscrowAmount: { - env: 'PROVER_MINIMUM_ESCROW_AMOUNT', - description: - 'Minimum amount to ensure is staked in the escrow contract for this prover. Prover node will top up whenever escrow falls below this number.', - ...bigintConfigHelper(100000n), - }, - proverTargetEscrowAmount: { - env: 'PROVER_TARGET_ESCROW_AMOUNT', - description: - 'Target amount to ensure is staked in the escrow contract for this prover. Prover node will top up to this value. Defaults to twice the minimum amount.', - ...bigintConfigHelper(), - }, -}; - -export function getProverBondManagerConfigFromEnv(): ProverBondManagerConfig { - return getConfigFromMappings(proverBondManagerConfigMappings); -} diff --git a/yarn-project/prover-node/src/bond/escrow-contract.ts b/yarn-project/prover-node/src/bond/escrow-contract.ts deleted file mode 100644 index 3816b41f51ea..000000000000 --- a/yarn-project/prover-node/src/bond/escrow-contract.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { EthAddress } from '@aztec/circuits.js'; -import { type L1Clients } from '@aztec/ethereum'; -import { IProofCommitmentEscrowAbi } from '@aztec/l1-artifacts'; - -import { - type Chain, - type GetContractReturnType, - type HttpTransport, - type PrivateKeyAccount, - type WalletClient, - getContract, -} from 'viem'; - -export class EscrowContract { - private escrow: GetContractReturnType< - typeof IProofCommitmentEscrowAbi, - WalletClient - >; - - constructor(private readonly client: L1Clients['walletClient'], address: EthAddress) { - this.escrow = getContract({ address: address.toString(), abi: IProofCommitmentEscrowAbi, client }); - } - - /** Returns the deposit of the publisher sender address on the proof commitment escrow contract. */ - public async getProverDeposit() { - return await this.escrow.read.deposits([this.getSenderAddress().toString()]); - } - - /** - * Deposits the given amount of tokens into the proof commitment escrow contract. Returns once the tx is mined. - * @param amount - The amount to deposit. - */ - public async depositProverBond(amount: bigint) { - const hash = await this.escrow.write.deposit([amount]); - await this.client.waitForTransactionReceipt({ hash }); - } - - /** Returns the sender address for the client. */ - public getSenderAddress(): EthAddress { - return EthAddress.fromString(this.client.account.address); - } - - public getEscrowAddress(): EthAddress { - return EthAddress.fromString(this.escrow.address); - } - - public async getTokenAddress(): Promise { - return EthAddress.fromString(await this.escrow.read.token()); - } -} diff --git a/yarn-project/prover-node/src/bond/factory.ts b/yarn-project/prover-node/src/bond/factory.ts deleted file mode 100644 index 3152d3be2c98..000000000000 --- a/yarn-project/prover-node/src/bond/factory.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { type EthAddress } from '@aztec/circuits.js'; -import { type L1Clients } from '@aztec/ethereum'; -import { compact } from '@aztec/foundation/collection'; - -import { BondManager } from './bond-manager.js'; -import { type ProverBondManagerConfig, getProverBondManagerConfigFromEnv } from './config.js'; -import { EscrowContract } from './escrow-contract.js'; -import { TokenContract } from './token-contract.js'; - -export async function createBondManager( - escrowContractAddress: EthAddress, - client: L1Clients['walletClient'], - overrides: Partial = {}, -) { - const config = { ...getProverBondManagerConfigFromEnv(), ...compact(overrides) }; - const { proverMinimumEscrowAmount: minimumStake, proverTargetEscrowAmount: maybeTargetStake } = config; - const targetStake = maybeTargetStake ?? minimumStake * 2n; - - const escrow = new EscrowContract(client, escrowContractAddress); - - const tokenContractAddress = await escrow.getTokenAddress(); - const token = new TokenContract(client, tokenContractAddress); - - // Ensure the prover has enough balance to cover escrow and try to mint otherwise if on a dev environment - await token.ensureBalance(targetStake * 2n); - - return new BondManager(token, escrow, minimumStake, targetStake); -} diff --git a/yarn-project/prover-node/src/bond/index.ts b/yarn-project/prover-node/src/bond/index.ts deleted file mode 100644 index 0a1e5ba57fec..000000000000 --- a/yarn-project/prover-node/src/bond/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { BondManager } from './bond-manager.js'; -export * from './factory.js'; diff --git a/yarn-project/prover-node/src/bond/token-contract.ts b/yarn-project/prover-node/src/bond/token-contract.ts deleted file mode 100644 index b45727e32bd9..000000000000 --- a/yarn-project/prover-node/src/bond/token-contract.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { EthAddress } from '@aztec/circuits.js'; -import { type L1Clients } from '@aztec/ethereum'; -import { createLogger } from '@aztec/foundation/log'; -import { IERC20Abi, TestERC20Abi } from '@aztec/l1-artifacts'; - -import { - type Chain, - type GetContractReturnType, - type HttpTransport, - type PrivateKeyAccount, - type WalletClient, - getContract, -} from 'viem'; - -const MAX_ALLOWANCE = (1n << 256n) - 1n; -const MIN_ALLOWANCE = 1n << 255n; - -export class TokenContract { - private token: GetContractReturnType>; - private logger = createLogger('prover-node:token-contract'); - - constructor(private readonly client: L1Clients['walletClient'], address: EthAddress) { - this.token = getContract({ address: address.toString(), abi: IERC20Abi, client }); - } - - /** - * Ensures the allowed address has near-maximum allowance, or sets it otherwise. - * Returns once allowance tx is mined successfully. - * @param allowed - Who to allow. - */ - public async ensureAllowance(allowed: EthAddress) { - const allowance = await this.token.read.allowance([this.getSenderAddress().toString(), allowed.toString()]); - if (allowance < MIN_ALLOWANCE) { - this.logger.verbose(`Approving max allowance for ${allowed.toString()}`); - const hash = await this.token.write.approve([allowed.toString(), MAX_ALLOWANCE]); - await this.client.waitForTransactionReceipt({ hash }); - } - } - - /** - * Checks the sender address has enough balance. - * If it doesn't, it tries calling a `mint` method, available on testing environments. - * If it can't, it throws an error. - * @param amount - The balance to ensure. - */ - public async ensureBalance(amount: bigint) { - const balance = await this.getBalance(); - if (balance < amount) { - this.logger.verbose(`Balance ${balance} is below required ${amount}. Attempting mint.`); - const testToken = getContract({ address: this.token.address, abi: TestERC20Abi, client: this.client }); - try { - await testToken.simulate.mint([this.getSenderAddress().toString(), amount - balance]); - const hash = await testToken.write.mint([this.getSenderAddress().toString(), amount - balance]); - await this.client.waitForTransactionReceipt({ hash }); - this.logger.verbose(`Minted ${amount - balance} test tokens`); - } catch (err) { - this.logger.warn(`Error minting test tokens: ${err}`); - throw new Error(`Insufficient balance for ${this.getSenderAddress().toString()}: ${balance} < ${amount}`); - } - } - } - - /** Returns the sender address. */ - public getSenderAddress(): EthAddress { - return EthAddress.fromString(this.client.account.address); - } - - /** Returns the balance of the sender. */ - public async getBalance() { - return await this.token.read.balanceOf([this.getSenderAddress().toString()]); - } -} diff --git a/yarn-project/prover-node/src/config.ts b/yarn-project/prover-node/src/config.ts index 97baf1944983..bcee4b7cbcac 100644 --- a/yarn-project/prover-node/src/config.ts +++ b/yarn-project/prover-node/src/config.ts @@ -1,11 +1,6 @@ import { type ArchiverConfig, archiverConfigMappings, getArchiverConfigFromEnv } from '@aztec/archiver/config'; import { type ACVMConfig, type BBConfig } from '@aztec/bb-prover/config'; -import { - type ConfigMappingsType, - bigintConfigHelper, - getConfigFromMappings, - numberConfigHelper, -} from '@aztec/foundation/config'; +import { type ConfigMappingsType, getConfigFromMappings, numberConfigHelper } from '@aztec/foundation/config'; import { type DataStoreConfig, dataConfigMappings, getDataConfigFromEnv } from '@aztec/kv-store/config'; import { type P2PConfig, getP2PConfigFromEnv, p2pConfigMappings } from '@aztec/p2p/config'; import { @@ -30,7 +25,6 @@ import { } from '@aztec/sequencer-client/config'; import { type WorldStateConfig, getWorldStateConfigFromEnv, worldStateConfigMappings } from '@aztec/world-state/config'; -import { type ProverBondManagerConfig, proverBondManagerConfigMappings } from './bond/config.js'; import { type ProverCoordinationConfig, getTxProviderConfigFromEnv, @@ -45,8 +39,6 @@ export type ProverNodeConfig = ArchiverConfig & TxSenderConfig & DataStoreConfig & ProverCoordinationConfig & - ProverBondManagerConfig & - QuoteProviderConfig & SpecificProverNodeConfig; type SpecificProverNodeConfig = { @@ -58,12 +50,6 @@ type SpecificProverNodeConfig = { txGatheringMaxParallelRequests: number; }; -export type QuoteProviderConfig = { - quoteProviderBasisPointFee: number; - quoteProviderBondAmount: bigint; - quoteProviderUrl?: string; -}; - const specificProverNodeConfigMappings: ConfigMappingsType = { proverNodeMaxPendingJobs: { env: 'PROVER_NODE_MAX_PENDING_JOBS', @@ -97,24 +83,6 @@ const specificProverNodeConfigMappings: ConfigMappingsType = { - quoteProviderBasisPointFee: { - env: 'QUOTE_PROVIDER_BASIS_POINT_FEE', - description: 'The basis point fee to charge for providing quotes', - ...numberConfigHelper(100), - }, - quoteProviderBondAmount: { - env: 'QUOTE_PROVIDER_BOND_AMOUNT', - description: 'The bond amount to charge for providing quotes', - ...bigintConfigHelper(1000n), - }, - quoteProviderUrl: { - env: 'QUOTE_PROVIDER_URL', - description: - 'The URL of the remote quote provider. Overrides QUOTE_PROVIDER_BASIS_POINT_FEE and QUOTE_PROVIDER_BOND_AMOUNT.', - }, -}; - export const proverNodeConfigMappings: ConfigMappingsType = { ...dataConfigMappings, ...archiverConfigMappings, @@ -124,8 +92,6 @@ export const proverNodeConfigMappings: ConfigMappingsType = { ...getPublisherConfigMappings('PROVER'), ...getTxSenderConfigMappings('PROVER'), ...proverCoordinationConfigMappings, - ...quoteProviderConfigMappings, - ...proverBondManagerConfigMappings, ...specificProverNodeConfigMappings, }; @@ -139,9 +105,7 @@ export function getProverNodeConfigFromEnv(): ProverNodeConfig { ...getPublisherConfigFromEnv('PROVER'), ...getTxSenderConfigFromEnv('PROVER'), ...getTxProviderConfigFromEnv(), - ...getConfigFromMappings(quoteProviderConfigMappings), ...getConfigFromMappings(specificProverNodeConfigMappings), - ...getConfigFromMappings(proverBondManagerConfigMappings), }; } diff --git a/yarn-project/prover-node/src/factory.ts b/yarn-project/prover-node/src/factory.ts index c9d10f802162..20209dfd7de3 100644 --- a/yarn-project/prover-node/src/factory.ts +++ b/yarn-project/prover-node/src/factory.ts @@ -3,28 +3,18 @@ import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob- import { type ProverCoordination, type ProvingJobBroker } from '@aztec/circuit-types'; import { EpochCache } from '@aztec/epoch-cache'; import { L1TxUtils, RollupContract, createEthereumChain, createL1Clients } from '@aztec/ethereum'; -import { Buffer32 } from '@aztec/foundation/buffer'; -import { EthAddress } from '@aztec/foundation/eth-address'; import { type Logger, createLogger } from '@aztec/foundation/log'; import { type DataStoreConfig } from '@aztec/kv-store/config'; -import { RollupAbi } from '@aztec/l1-artifacts'; import { createProverClient } from '@aztec/prover-client'; import { createAndStartProvingBroker } from '@aztec/prover-client/broker'; import { type TelemetryClient, getTelemetryClient } from '@aztec/telemetry-client'; import { createWorldStateSynchronizer } from '@aztec/world-state'; -import { createPublicClient, getAddress, getContract, http } from 'viem'; - -import { createBondManager } from './bond/factory.js'; -import { type ProverNodeConfig, type QuoteProviderConfig } from './config.js'; -import { ClaimsMonitor } from './monitors/claims-monitor.js'; +import { type ProverNodeConfig } from './config.js'; import { EpochMonitor } from './monitors/epoch-monitor.js'; import { createProverCoordination } from './prover-coordination/factory.js'; import { ProverNodePublisher } from './prover-node-publisher.js'; import { ProverNode, type ProverNodeOptions } from './prover-node.js'; -import { HttpQuoteProvider } from './quote-provider/http.js'; -import { SimpleQuoteProvider } from './quote-provider/simple.js'; -import { QuoteSigner } from './quote-signer.js'; /** Creates a new prover node given a config. */ export async function createProverNode( @@ -64,7 +54,7 @@ export async function createProverNode( const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config); - // If config.p2pEnabled is true, createProverCoordination will create a p2p client where quotes will be shared and tx's requested + // If config.p2pEnabled is true, createProverCoordination will create a p2p client where txs are requested // If config.p2pEnabled is false, createProverCoordination request information from the AztecNode const proverCoordination = await createProverCoordination(config, { aztecNodeTxProvider: deps.aztecNodeTxProvider, @@ -74,9 +64,6 @@ export async function createProverNode( telemetry, }); - const quoteProvider = createQuoteProvider(config); - const quoteSigner = createQuoteSigner(config); - const proverNodeConfig: ProverNodeOptions = { maxPendingJobs: config.proverNodeMaxPendingJobs, pollingIntervalMs: config.proverNodePollingIntervalMs, @@ -86,12 +73,8 @@ export async function createProverNode( txGatheringTimeoutMs: config.txGatheringTimeoutMs, }; - const claimsMonitor = new ClaimsMonitor(publisher, proverNodeConfig, telemetry); const epochMonitor = new EpochMonitor(archiver, proverNodeConfig, telemetry); - const escrowContractAddress = await rollupContract.getProofCommitmentEscrow(); - const bondManager = await createBondManager(EthAddress.fromString(escrowContractAddress), walletClient, config); - return new ProverNode( prover, publisher, @@ -100,29 +83,8 @@ export async function createProverNode( archiver, worldStateSynchronizer, proverCoordination, - quoteProvider, - quoteSigner, - claimsMonitor, epochMonitor, - bondManager, proverNodeConfig, telemetry, ); } - -function createQuoteProvider(config: QuoteProviderConfig) { - return config.quoteProviderUrl - ? new HttpQuoteProvider(config.quoteProviderUrl) - : new SimpleQuoteProvider(config.quoteProviderBasisPointFee, config.quoteProviderBondAmount); -} - -function createQuoteSigner(config: ProverNodeConfig) { - // REFACTOR: We need a package that just returns an instance of a rollup contract ready to use - const { l1RpcUrl: rpcUrl, l1ChainId: chainId, l1Contracts } = config; - const chain = createEthereumChain(rpcUrl, chainId); - const client = createPublicClient({ chain: chain.chainInfo, transport: http(chain.rpcUrl) }); - const address = getAddress(l1Contracts.rollupAddress.toString()); - const rollupContract = getContract({ address, abi: RollupAbi, client }); - const privateKey = config.publisherPrivateKey; - return QuoteSigner.new(Buffer32.fromString(privateKey), rollupContract); -} diff --git a/yarn-project/prover-node/src/job/epoch-proving-job.ts b/yarn-project/prover-node/src/job/epoch-proving-job.ts index a7126f1fdad7..26c5588f894a 100644 --- a/yarn-project/prover-node/src/job/epoch-proving-job.ts +++ b/yarn-project/prover-node/src/job/epoch-proving-job.ts @@ -63,6 +63,10 @@ export class EpochProvingJob implements Traceable { return this.state; } + public getEpochNumber(): bigint { + return this.epochNumber; + } + /** * Proves the given epoch and submits the proof to L1. */ diff --git a/yarn-project/prover-node/src/monitors/claims-monitor.test.ts b/yarn-project/prover-node/src/monitors/claims-monitor.test.ts deleted file mode 100644 index 4dcea17acb67..000000000000 --- a/yarn-project/prover-node/src/monitors/claims-monitor.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { type EpochProofClaim } from '@aztec/circuit-types'; -import { EthAddress } from '@aztec/circuits.js'; -import { sleep } from '@aztec/foundation/sleep'; - -import { type MockProxy, mock } from 'jest-mock-extended'; - -import { type ProverNodePublisher } from '../prover-node-publisher.js'; -import { ClaimsMonitor, type ClaimsMonitorHandler } from './claims-monitor.js'; - -describe('ClaimsMonitor', () => { - let l1Publisher: MockProxy; - let handler: MockProxy; - let claimsMonitor: ClaimsMonitor; - - let publisherAddress: EthAddress; - - beforeEach(() => { - l1Publisher = mock(); - handler = mock(); - - publisherAddress = EthAddress.random(); - l1Publisher.getSenderAddress.mockReturnValue(publisherAddress); - - claimsMonitor = new ClaimsMonitor(l1Publisher, { pollingIntervalMs: 10 }); - }); - - afterEach(async () => { - await claimsMonitor.stop(); - }); - - const makeEpochProofClaim = (epoch: number, bondProvider: EthAddress): MockProxy => { - return { - basisPointFee: 100n, - bondAmount: 10n, - bondProvider, - epochToProve: BigInt(epoch), - proposerClaimant: EthAddress.random(), - }; - }; - - it('should handle a new claim if it belongs to the prover', async () => { - const proofClaim = makeEpochProofClaim(1, publisherAddress); - l1Publisher.getProofClaim.mockResolvedValue(proofClaim); - - claimsMonitor.start(handler); - await sleep(100); - - expect(handler.handleClaim).toHaveBeenCalledWith(proofClaim); - expect(handler.handleClaim).toHaveBeenCalledTimes(1); - }); - - it('should not handle a new claim if it does not belong to the prover', async () => { - const proofClaim = makeEpochProofClaim(1, EthAddress.random()); - l1Publisher.getProofClaim.mockResolvedValue(proofClaim); - - claimsMonitor.start(handler); - await sleep(100); - - expect(handler.handleClaim).not.toHaveBeenCalled(); - }); - - it('should only trigger when a new claim is seen', async () => { - const proofClaim = makeEpochProofClaim(1, publisherAddress); - l1Publisher.getProofClaim.mockResolvedValue(proofClaim); - - claimsMonitor.start(handler); - await sleep(100); - - expect(handler.handleClaim).toHaveBeenCalledWith(proofClaim); - expect(handler.handleClaim).toHaveBeenCalledTimes(1); - - const proofClaim2 = makeEpochProofClaim(2, publisherAddress); - l1Publisher.getProofClaim.mockResolvedValue(proofClaim2); - await sleep(100); - - expect(handler.handleClaim).toHaveBeenCalledWith(proofClaim2); - expect(handler.handleClaim).toHaveBeenCalledTimes(2); - }); -}); diff --git a/yarn-project/prover-node/src/monitors/claims-monitor.ts b/yarn-project/prover-node/src/monitors/claims-monitor.ts deleted file mode 100644 index 0aeb7c6a58fe..000000000000 --- a/yarn-project/prover-node/src/monitors/claims-monitor.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { type EpochProofClaim } from '@aztec/circuit-types'; -import { type EthAddress } from '@aztec/circuits.js'; -import { createLogger } from '@aztec/foundation/log'; -import { RunningPromise } from '@aztec/foundation/running-promise'; -import { - type TelemetryClient, - type Traceable, - type Tracer, - getTelemetryClient, - trackSpan, -} from '@aztec/telemetry-client'; - -import { type ProverNodePublisher } from '../prover-node-publisher.js'; - -export interface ClaimsMonitorHandler { - handleClaim(proofClaim: EpochProofClaim): Promise; -} - -export class ClaimsMonitor implements Traceable { - private runningPromise: RunningPromise; - private log = createLogger('prover-node:claims-monitor'); - - private handler: ClaimsMonitorHandler | undefined; - private lastClaimEpochNumber: bigint | undefined; - - public readonly tracer: Tracer; - - constructor( - private readonly l1Publisher: ProverNodePublisher, - private options: { pollingIntervalMs: number }, - telemetry: TelemetryClient = getTelemetryClient(), - ) { - this.tracer = telemetry.getTracer('ClaimsMonitor'); - this.runningPromise = new RunningPromise(this.work.bind(this), this.log, this.options.pollingIntervalMs); - } - - public start(handler: ClaimsMonitorHandler) { - this.handler = handler; - this.runningPromise.start(); - this.log.info(`Started ClaimsMonitor with prover address ${this.getProverAddress().toString()}`, this.options); - } - - public async stop() { - this.log.verbose('Stopping ClaimsMonitor'); - await this.runningPromise.stop(); - this.log.info('Stopped ClaimsMonitor'); - } - - @trackSpan('ClaimsMonitor.work') - public async work() { - const proofClaim = await this.l1Publisher.getProofClaim(); - if (!proofClaim) { - this.log.trace(`Found no proof claim`); - return; - } - - if (this.lastClaimEpochNumber === undefined || proofClaim.epochToProve > this.lastClaimEpochNumber) { - this.log.verbose(`Found new claim for epoch ${proofClaim.epochToProve} by ${proofClaim.bondProvider.toString()}`); - if (proofClaim.bondProvider.equals(this.getProverAddress())) { - await this.handler?.handleClaim(proofClaim); - } - this.lastClaimEpochNumber = proofClaim.epochToProve; - } - } - - protected getProverAddress(): EthAddress { - return this.l1Publisher.getSenderAddress(); - } -} diff --git a/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts b/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts index f7a0d7dc4067..f9333c861509 100644 --- a/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts +++ b/yarn-project/prover-node/src/monitors/epoch-monitor.test.ts @@ -32,7 +32,7 @@ describe('EpochMonitor', () => { epochMonitor.start(handler); await sleep(100); - expect(handler.handleInitialEpochSync).toHaveBeenCalledWith(9n); + expect(handler.handleEpochCompleted).toHaveBeenCalledWith(9n); }); it('does not trigger initial epoch sync on epoch zero', async () => { @@ -40,7 +40,7 @@ describe('EpochMonitor', () => { epochMonitor.start(handler); await sleep(100); - expect(handler.handleInitialEpochSync).not.toHaveBeenCalled(); + expect(handler.handleEpochCompleted).not.toHaveBeenCalled(); }); it('triggers epoch completion', async () => { @@ -49,17 +49,18 @@ describe('EpochMonitor', () => { epochMonitor.start(handler); await sleep(100); - expect(handler.handleEpochCompleted).not.toHaveBeenCalled(); + expect(handler.handleEpochCompleted).toHaveBeenCalledWith(9n); + expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(1); lastEpochComplete = 10n; await sleep(100); expect(handler.handleEpochCompleted).toHaveBeenCalledWith(10n); - expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(1); + expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(2); lastEpochComplete = 11n; await sleep(100); expect(handler.handleEpochCompleted).toHaveBeenCalledWith(11n); - expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(2); + expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(3); }); it('triggers epoch completion if initial epoch was already complete', async () => { @@ -69,8 +70,8 @@ describe('EpochMonitor', () => { epochMonitor.start(handler); await sleep(100); - expect(handler.handleInitialEpochSync).toHaveBeenCalledWith(9n); + expect(handler.handleEpochCompleted).toHaveBeenCalledWith(9n); expect(handler.handleEpochCompleted).toHaveBeenCalledWith(10n); - expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(1); + expect(handler.handleEpochCompleted).toHaveBeenCalledTimes(2); }); }); diff --git a/yarn-project/prover-node/src/monitors/epoch-monitor.ts b/yarn-project/prover-node/src/monitors/epoch-monitor.ts index a285162a6629..27fa6c00c33a 100644 --- a/yarn-project/prover-node/src/monitors/epoch-monitor.ts +++ b/yarn-project/prover-node/src/monitors/epoch-monitor.ts @@ -10,7 +10,6 @@ import { } from '@aztec/telemetry-client'; export interface EpochMonitorHandler { - handleInitialEpochSync(epochNumber: bigint): Promise; handleEpochCompleted(epochNumber: bigint): Promise; } @@ -48,7 +47,7 @@ export class EpochMonitor implements Traceable { if (!this.latestEpochNumber) { const epochNumber = await this.l2BlockSource.getL2EpochNumber(); if (epochNumber > 0n) { - await this.handler?.handleInitialEpochSync(epochNumber - 1n); + await this.handler?.handleEpochCompleted(epochNumber - 1n); } this.latestEpochNumber = epochNumber; return; diff --git a/yarn-project/prover-node/src/monitors/index.ts b/yarn-project/prover-node/src/monitors/index.ts index f3b5be62922c..e46352510266 100644 --- a/yarn-project/prover-node/src/monitors/index.ts +++ b/yarn-project/prover-node/src/monitors/index.ts @@ -1,2 +1 @@ -export * from './claims-monitor.js'; export * from './epoch-monitor.js'; diff --git a/yarn-project/prover-node/src/prover-node-publisher.ts b/yarn-project/prover-node/src/prover-node-publisher.ts index ed3be11af281..147d2512832b 100644 --- a/yarn-project/prover-node/src/prover-node-publisher.ts +++ b/yarn-project/prover-node/src/prover-node-publisher.ts @@ -85,10 +85,6 @@ export class ProverNodePublisher { return EthAddress.fromString(this.l1TxUtils.getSenderAddress()); } - public getProofClaim() { - return this.rollupContract.getProofClaim(); - } - public async submitEpochProof(args: { epochNumber: number; fromBlock: number; @@ -205,11 +201,12 @@ export class ProverNodePublisher { const txArgs = [ { - epochSize: argsArray[0], - args: argsArray[1], - fees: argsArray[2], - blobPublicInputs: argsArray[3], - aggregationObject: argsArray[4], + start: argsArray[0], + end: argsArray[1], + args: argsArray[2], + fees: argsArray[3], + blobPublicInputs: argsArray[4], + aggregationObject: argsArray[5], proof: proofHex, }, ] as const; @@ -252,7 +249,8 @@ export class ProverNodePublisher { proof: Proof; }) { return [ - BigInt(args.toBlock - args.fromBlock + 1), + BigInt(args.fromBlock), + BigInt(args.toBlock), [ args.publicInputs.previousArchive.root.toString(), args.publicInputs.endArchive.root.toString(), diff --git a/yarn-project/prover-node/src/prover-node.test.ts b/yarn-project/prover-node/src/prover-node.test.ts index 7ef130de9de1..a9f220ec588e 100644 --- a/yarn-project/prover-node/src/prover-node.test.ts +++ b/yarn-project/prover-node/src/prover-node.test.ts @@ -1,43 +1,27 @@ import { EmptyL1RollupConstants, - type EpochProofClaim, - EpochProofQuote, - EpochProofQuotePayload, type EpochProverManager, + type EpochProvingJobState, type L1ToL2MessageSource, L2Block, type L2BlockSource, type MerkleTreeWriteOperations, - P2PClientType, type ProverCoordination, type Tx, - type TxHash, WorldStateRunningState, type WorldStateSynchronizer, } from '@aztec/circuit-types'; import { type ContractDataSource, EthAddress } from '@aztec/circuits.js'; -import { type EpochCache } from '@aztec/epoch-cache'; import { timesParallel } from '@aztec/foundation/collection'; -import { Signature } from '@aztec/foundation/eth-signature'; -import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; -import { openTmpStore } from '@aztec/kv-store/lmdb-v2'; -import { type BootstrapNode, InMemoryTxPool, MemoryEpochProofQuotePool, P2PClient } from '@aztec/p2p'; -import { createBootstrapNode, createTestLibP2PService } from '@aztec/p2p/test-helpers'; import { type PublicProcessorFactory } from '@aztec/simulator/server'; -import { getTelemetryClient } from '@aztec/telemetry-client'; -import { jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import { type BondManager } from './bond/bond-manager.js'; import { type EpochProvingJob } from './job/epoch-proving-job.js'; -import { ClaimsMonitor } from './monitors/claims-monitor.js'; import { EpochMonitor } from './monitors/epoch-monitor.js'; import { type ProverNodePublisher } from './prover-node-publisher.js'; import { ProverNode, type ProverNodeOptions } from './prover-node.js'; -import { type QuoteProvider } from './quote-provider/index.js'; -import { type QuoteSigner } from './quote-signer.js'; describe('prover-node', () => { // Prover node dependencies @@ -49,20 +33,11 @@ describe('prover-node', () => { let worldState: MockProxy; let coordination: ProverCoordination; let mockCoordination: MockProxy; - let quoteProvider: MockProxy; - let quoteSigner: MockProxy; - let bondManager: MockProxy; let config: ProverNodeOptions; // Subject under test let proverNode: TestProverNode; - // Quote returned by the provider by default and its completed quote - let partialQuote: Pick; - - // Sample claim - let claim: MockProxy; - // Blocks returned by the archiver let blocks: L2Block[]; @@ -76,17 +51,7 @@ describe('prover-node', () => { epochNumber: bigint; }[]; - const toQuotePayload = ( - epoch: bigint, - partialQuote: Pick, - ) => EpochProofQuotePayload.from({ ...partialQuote, prover: address, epochToProve: epoch }); - - const toExpectedQuote = ( - epoch: bigint, - quote: Pick = partialQuote, - ) => expect.objectContaining({ payload: toQuotePayload(epoch, quote) }); - - const createProverNode = (claimsMonitor: ClaimsMonitor, epochMonitor: EpochMonitor) => + const createProverNode = (epochMonitor: EpochMonitor) => new TestProverNode( prover, publisher, @@ -95,11 +60,7 @@ describe('prover-node', () => { contractDataSource, worldState, coordination, - quoteProvider, - quoteSigner, - claimsMonitor, epochMonitor, - bondManager, config, ); @@ -112,9 +73,6 @@ describe('prover-node', () => { worldState = mock(); mockCoordination = mock(); coordination = mockCoordination; - quoteProvider = mock(); - quoteSigner = mock(); - bondManager = mock(); config = { maxPendingJobs: 3, @@ -136,13 +94,6 @@ describe('prover-node', () => { address = EthAddress.random(); publisher.getSenderAddress.mockReturnValue(address); - // Quote provider returns a mock - partialQuote = { basisPointFee: 100, bondAmount: 0n, validUntilSlot: 30n }; - quoteProvider.getQuote.mockResolvedValue(partialQuote); - - // Signer returns an empty signature - quoteSigner.sign.mockImplementation(payload => Promise.resolve(new EpochProofQuote(payload, Signature.empty()))); - // We create 3 fake blocks with 1 tx effect each blocks = await timesParallel(3, async i => await L2Block.random(i + 20, 1)); @@ -168,9 +119,6 @@ describe('prover-node', () => { Promise.resolve(hashes.map(hash => mock({ getTxHash: () => Promise.resolve(hash) }))), ); - // A sample claim - claim = { epochToProve: 10n, bondProvider: address } as EpochProofClaim; - jobs = []; }); @@ -179,299 +127,87 @@ describe('prover-node', () => { }); describe('with mocked monitors', () => { - let claimsMonitor: MockProxy; let epochMonitor: MockProxy; beforeEach(() => { - claimsMonitor = mock(); epochMonitor = mock(); - proverNode = createProverNode(claimsMonitor, epochMonitor); + proverNode = createProverNode(epochMonitor); }); - it('sends a quote on a finished epoch', async () => { + it('starts a proof on a finished epoch', async () => { await proverNode.handleEpochCompleted(10n); - - expect(quoteProvider.getQuote).toHaveBeenCalledWith(10, blocks); - expect(quoteSigner.sign).toHaveBeenCalledWith(expect.objectContaining(partialQuote)); - expect(coordination.addEpochProofQuote).toHaveBeenCalledTimes(1); - - expect(coordination.addEpochProofQuote).toHaveBeenCalledWith(toExpectedQuote(10n)); + expect(jobs[0].epochNumber).toEqual(10n); }); - it('does not send a quote if there are no blocks in the epoch', async () => { + it('does not start a proof if there are no blocks in the epoch', async () => { l2BlockSource.getBlocksForEpoch.mockResolvedValue([]); await proverNode.handleEpochCompleted(10n); - expect(coordination.addEpochProofQuote).not.toHaveBeenCalled(); + expect(jobs.length).toEqual(0); }); - it('does not send a quote if there is a tx missing from coordinator', async () => { + it('does not start a proof if there is a tx missing from coordinator', async () => { mockCoordination.getTxsByHash.mockResolvedValue([]); await proverNode.handleEpochCompleted(10n); - expect(coordination.addEpochProofQuote).not.toHaveBeenCalled(); - }); - - it('does not send a quote on a finished epoch if the provider does not return one', async () => { - quoteProvider.getQuote.mockResolvedValue(undefined); - await proverNode.handleEpochCompleted(10n); - - expect(quoteSigner.sign).not.toHaveBeenCalled(); - expect(coordination.addEpochProofQuote).not.toHaveBeenCalled(); - }); - - it('starts proving on a new claim', async () => { - await proverNode.handleClaim(claim); - - expect(jobs[0].epochNumber).toEqual(10n); + expect(jobs.length).toEqual(0); }); it('does not prove the same epoch twice', async () => { - await proverNode.handleClaim(claim); - await proverNode.handleClaim(claim); + await proverNode.handleEpochCompleted(10n); + await proverNode.handleEpochCompleted(10n); expect(jobs.length).toEqual(1); }); - - it('sends a quote on the initial sync if there is no claim', async () => { - await proverNode.handleInitialEpochSync(10n); - - expect(coordination.addEpochProofQuote).toHaveBeenCalledTimes(1); - }); - - it('sends a quote on the initial sync if there is a claim for an older epoch', async () => { - const claim = { epochToProve: 9n, bondProvider: EthAddress.random() } as EpochProofClaim; - publisher.getProofClaim.mockResolvedValue(claim); - await proverNode.handleInitialEpochSync(10n); - - expect(coordination.addEpochProofQuote).toHaveBeenCalledTimes(1); - }); - - it('does not send a quote on the initial sync if there is already a claim', async () => { - const claim = { epochToProve: 10n, bondProvider: EthAddress.random() } as EpochProofClaim; - publisher.getProofClaim.mockResolvedValue(claim); - await proverNode.handleInitialEpochSync(10n); - - expect(coordination.addEpochProofQuote).not.toHaveBeenCalled(); - }); - - it('starts proving if there is a claim sent by us', async () => { - l2BlockSource.getProvenL2EpochNumber.mockResolvedValue(9); - await proverNode.handleClaim(claim); - - expect(jobs[0].epochNumber).toEqual(10n); - }); - - it('does not start proving if there is a claim sent by us but proof has already landed', async () => { - l2BlockSource.getProvenL2EpochNumber.mockResolvedValue(10); - await proverNode.handleClaim(claim); - - expect(jobs.length).toEqual(0); - }); }); describe('with actual monitors', () => { - let claimsMonitor: ClaimsMonitor; let epochMonitor: EpochMonitor; // Answers l2BlockSource.isEpochComplete, queried from the epoch monitor let lastEpochComplete: bigint = 0n; beforeEach(() => { - claimsMonitor = new ClaimsMonitor(publisher, config); epochMonitor = new EpochMonitor(l2BlockSource, config); l2BlockSource.isEpochComplete.mockImplementation(epochNumber => Promise.resolve(epochNumber <= lastEpochComplete), ); - proverNode = createProverNode(claimsMonitor, epochMonitor); + proverNode = createProverNode(epochMonitor); }); - it('sends a quote on initial sync', async () => { - l2BlockSource.getL2EpochNumber.mockResolvedValue(10n); - - await proverNode.start(); - await sleep(100); - expect(coordination.addEpochProofQuote).toHaveBeenCalledTimes(1); - }); - - it('starts proving if there is a claim during initial sync', async () => { + it('starts a proof during initial sync', async () => { l2BlockSource.getL2EpochNumber.mockResolvedValue(11n); - publisher.getProofClaim.mockResolvedValue(claim); - await proverNode.start(); + proverNode.start(); await sleep(100); expect(jobs[0].epochNumber).toEqual(10n); expect(jobs.length).toEqual(1); }); - it('does not start proving if txs are not all available', async () => { + it('does not start a proof if txs are not all available', async () => { l2BlockSource.getL2EpochNumber.mockResolvedValue(11n); - publisher.getProofClaim.mockResolvedValue(claim); mockCoordination.getTxsByHash.mockResolvedValue([]); - await proverNode.start(); + proverNode.start(); await sleep(2000); expect(jobs).toHaveLength(0); }); - it('does not start proving if there is a claim for proven epoch during initial sync', async () => { - l2BlockSource.getProvenL2EpochNumber.mockResolvedValue(10); - publisher.getProofClaim.mockResolvedValue(claim); - - await proverNode.start(); - await sleep(100); - - expect(jobs.length).toEqual(0); - }); - - it('sends another quote when a new epoch is completed', async () => { + it('starts a proof when a new epoch is completed', async () => { lastEpochComplete = 10n; l2BlockSource.getL2EpochNumber.mockResolvedValue(11n); - await proverNode.start(); + proverNode.start(); await sleep(100); lastEpochComplete = 11n; await sleep(100); - expect(coordination.addEpochProofQuote).toHaveBeenCalledTimes(2); - expect(coordination.addEpochProofQuote).toHaveBeenCalledWith(toExpectedQuote(10n)); - expect(coordination.addEpochProofQuote).toHaveBeenCalledWith(toExpectedQuote(11n)); - }); - - it('starts proving when a claim is seen', async () => { - publisher.getProofClaim.mockResolvedValue(claim); - - await proverNode.start(); - await sleep(100); - expect(jobs[0].epochNumber).toEqual(10n); - }); - }); - - // Things to test - // - Another aztec node receives the proof quote via p2p - // - The prover node can get the it is missing via p2p, or it has them in it's mempool - describe('using a p2p coordination', () => { - let bootnode: BootstrapNode; - let epochCache: MockProxy; - let p2pClient: P2PClient; - let otherP2PClient: P2PClient; - - const createP2PClient = async (bootnodeAddr: string, port: number) => { - const mempools = { - txPool: new InMemoryTxPool(), - epochProofQuotePool: new MemoryEpochProofQuotePool(), - }; - epochCache = mock(); - const libp2pService = await createTestLibP2PService( - P2PClientType.Prover, - [bootnodeAddr], - l2BlockSource, - worldState, - epochCache, - mempools, - getTelemetryClient(), - port, - ); - const kvStore = await openTmpStore('test'); - return new P2PClient(P2PClientType.Prover, kvStore, l2BlockSource, mempools, libp2pService); - }; - - beforeEach(async () => { - bootnode = await createBootstrapNode(40400); - await sleep(1000); - - const bootnodeAddr = bootnode.getENR().encodeTxt(); - p2pClient = await createP2PClient(bootnodeAddr, 8080); - otherP2PClient = await createP2PClient(bootnodeAddr, 8081); - - // Set the p2p client to be the coordination method - coordination = p2pClient; - - // But still mock getTxByHash - const mockGetTxByHash = (hash: TxHash) => Promise.resolve(mock({ getTxHash: () => Promise.resolve(hash) })); - jest.spyOn(p2pClient, 'getTxByHash').mockImplementation(mockGetTxByHash); - jest.spyOn(otherP2PClient, 'getTxByHash').mockImplementation(mockGetTxByHash); - - // And getTxsByHash just for good measure - const mockGetTxsByHash = (hashes: TxHash[]) => - Promise.resolve(hashes.map(hash => mock({ getTxHash: () => Promise.resolve(hash) }))); - jest.spyOn(p2pClient, 'getTxsByHash').mockImplementation(mockGetTxsByHash); - jest.spyOn(otherP2PClient, 'getTxsByHash').mockImplementation(mockGetTxsByHash); - - await Promise.all([p2pClient.start(), otherP2PClient.start()]); - - // Sleep to enable peer discovery - await sleep(3000); - }, 10000); - - afterEach(async () => { - await bootnode.stop(); - await p2pClient?.stop(); - await otherP2PClient.stop(); - }); - - describe('with mocked monitors', () => { - let claimsMonitor: MockProxy; - let epochMonitor: MockProxy; - - beforeEach(() => { - claimsMonitor = mock(); - epochMonitor = mock(); - - proverNode = createProverNode(claimsMonitor, epochMonitor); - }); - - afterEach(async () => { - await proverNode.stop(); - }); - - it('should send a proof quote via p2p to another node', async () => { - const epochNumber = 10n; - epochCache.getEpochAndSlotNow.mockReturnValue({ - epoch: epochNumber, - slot: epochNumber * 2n, - ts: BigInt(Date.now()), - }); - - await retryUntil( - async () => { - return (await proverNode.getP2P()!.getPeers()).length > 0; - }, - 'wait for peers to connect', - 20, - 1, - ); - - // Check that the p2p client receives the quote (casted as any to access private property) - const p2pEpochReceivedSpy = jest.spyOn((otherP2PClient as any).p2pService, 'processEpochProofQuoteFromPeer'); - - // Check the other node's pool has no quotes yet - const peerInitialState = await otherP2PClient.getEpochProofQuotes(epochNumber); - expect(peerInitialState.length).toEqual(0); - - await proverNode.handleEpochCompleted(epochNumber); - - // Wait for message to be propagated - await retryUntil( - // eslint-disable-next-line require-await - async () => { - // Check the other node received a quote via p2p - return p2pEpochReceivedSpy.mock.calls.length > 0; - }, - 'Waiting for quote to be received', - 20, - 1, - ); - - // We should be able to retreive the quote from the other node - const peerFinalStateQuotes = await otherP2PClient.getEpochProofQuotes(epochNumber); - expect(peerFinalStateQuotes[0]).toEqual(toExpectedQuote(epochNumber)); - }); + expect(jobs[1].epochNumber).toEqual(11n); }); }); @@ -493,5 +229,11 @@ describe('prover-node', () => { public override triggerMonitors() { return super.triggerMonitors(); } + + public override getJobs(): Promise<{ uuid: string; status: EpochProvingJobState; epochNumber: number }[]> { + return Promise.resolve( + jobs.map(j => ({ uuid: j.job.getId(), status: j.job.getState(), epochNumber: Number(j.epochNumber) })), + ); + } } }); diff --git a/yarn-project/prover-node/src/prover-node.ts b/yarn-project/prover-node/src/prover-node.ts index 4b3a1e3b3c21..eed1c08da00d 100644 --- a/yarn-project/prover-node/src/prover-node.ts +++ b/yarn-project/prover-node/src/prover-node.ts @@ -1,8 +1,6 @@ import { - type EpochProofClaim, - type EpochProofQuote, - EpochProofQuotePayload, type EpochProverManager, + EpochProvingJobTerminalState, type L1ToL2MessageSource, type L2Block, type L2BlockSource, @@ -34,14 +32,10 @@ import { trackSpan, } from '@aztec/telemetry-client'; -import { type BondManager } from './bond/bond-manager.js'; import { EpochProvingJob, type EpochProvingJobState } from './job/epoch-proving-job.js'; import { ProverNodeMetrics } from './metrics.js'; -import { type ClaimsMonitor, type ClaimsMonitorHandler } from './monitors/claims-monitor.js'; import { type EpochMonitor, type EpochMonitorHandler } from './monitors/epoch-monitor.js'; import { type ProverNodePublisher } from './prover-node-publisher.js'; -import { type QuoteProvider } from './quote-provider/index.js'; -import { type QuoteSigner } from './quote-signer.js'; export type ProverNodeOptions = { pollingIntervalMs: number; @@ -58,7 +52,7 @@ export type ProverNodeOptions = { * from a tx source in the p2p network or an external node, re-executes their public functions, creates a rollup * proof for the epoch, and submits it to L1. */ -export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, ProverNodeApi, Traceable { +export class ProverNode implements EpochMonitorHandler, ProverNodeApi, Traceable { private log = createLogger('prover-node'); private dateProvider = new DateProvider(); @@ -81,11 +75,7 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr protected readonly contractDataSource: ContractDataSource, protected readonly worldState: WorldStateSynchronizer, protected readonly coordination: ProverCoordination & Maybe, - protected readonly quoteProvider: QuoteProvider, - protected readonly quoteSigner: QuoteSigner, - protected readonly claimsMonitor: ClaimsMonitor, protected readonly epochsMonitor: EpochMonitor, - protected readonly bondManager: BondManager, options: Partial = {}, protected readonly telemetryClient: TelemetryClient = getTelemetryClient(), ) { @@ -112,89 +102,23 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr return undefined; } - async handleClaim(proofClaim: EpochProofClaim): Promise { - if (proofClaim.epochToProve === this.latestEpochWeAreProving) { - this.log.verbose(`Already proving claim for epoch ${proofClaim.epochToProve}`); - return; - } - - const provenEpoch = await this.l2BlockSource.getProvenL2EpochNumber(); - if (provenEpoch !== undefined && proofClaim.epochToProve <= provenEpoch) { - this.log.verbose(`Claim for epoch ${proofClaim.epochToProve} is already proven`); - return; - } - - try { - await this.startProof(proofClaim.epochToProve); - this.latestEpochWeAreProving = proofClaim.epochToProve; - } catch (err) { - this.log.error(`Error handling claim for epoch ${proofClaim.epochToProve}`, err); - } - - try { - // Staked amounts are lowered after a claim, so this is a good time for doing a top-up if needed - await this.bondManager.ensureBond(); - } catch (err) { - this.log.error(`Error ensuring prover bond after handling claim for epoch ${proofClaim.epochToProve}`, err); - } - } - /** - * Handles the epoch number to prove when the prover node starts by checking if there - * is an existing claim for it. If not, it creates and sends a quote for it. - * @param epochNumber - The epoch immediately before the current one when the prover node starts. - */ - async handleInitialEpochSync(epochNumber: bigint): Promise { - try { - const claim = await this.publisher.getProofClaim(); - if (!claim || claim.epochToProve < epochNumber) { - this.log.verbose(`Handling epoch ${epochNumber} completed as initial sync`); - await this.handleEpochCompleted(epochNumber); - } - } catch (err) { - this.log.error(`Error handling initial epoch sync`, err); - } - } - - /** - * Handles an epoch being completed by sending a quote for proving it. + * Handles an epoch being completed by starting a proof for it if there are no active jobs for it. * @param epochNumber - The epoch number that was just completed. */ async handleEpochCompleted(epochNumber: bigint): Promise { try { - // Gather data for the epoch - const epochData = await this.gatherEpochData(epochNumber); - const { blocks } = epochData; - this.cachedEpochData = { epochNumber, ...epochData }; - - // Construct a quote for the epoch - const partialQuote = await this.quoteProvider.getQuote(Number(epochNumber), blocks); - if (!partialQuote) { - this.log.info(`No quote produced for epoch ${epochNumber}`); + this.log.debug('jobs', JSON.stringify(this.jobs, null, 2)); + const activeJobs = await this.getActiveJobsForEpoch(epochNumber); + if (activeJobs.length > 0) { + this.log.info(`Not starting proof for ${epochNumber} since there are active jobs`); return; } - - // Ensure we have deposited enough funds for sending this quote - await this.bondManager.ensureBond(partialQuote.bondAmount); - - // Assemble and sign full quote - const quote = EpochProofQuotePayload.from({ - ...partialQuote, - epochToProve: BigInt(epochNumber), - prover: this.publisher.getSenderAddress(), - validUntilSlot: partialQuote.validUntilSlot ?? BigInt(Number.MAX_SAFE_INTEGER), // Should we constrain this? - }); - const signed = await this.quoteSigner.sign(quote); - - // Send it to the coordinator - this.log.info( - `Sending quote for epoch ${epochNumber} with blocks ${blocks[0].number} to ${blocks.at(-1)!.number}`, - quote.toViemArgs(), - ); - await this.doSendEpochProofQuote(signed); + // TODO: we probably want to skip starting a proof if we are too far into the current epoch + await this.startProof(epochNumber); } catch (err) { if (err instanceof EmptyEpochError) { - this.log.info(`Not producing quote for ${epochNumber} since no blocks were found`); + this.log.info(`Not starting proof for ${epochNumber} since no blocks were found`); } else { this.log.error(`Error handling epoch completed`, err); } @@ -202,15 +126,12 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr } /** - * Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and sends - * quotes for them, as well as monitors the claims for the epochs it has sent quotes for and starts proving jobs. - * This method returns once the prover node has deposited an initial bond into the escrow contract. + * Starts the prover node so it periodically checks for unproven epochs in the unfinalised chain from L1 and + * starts proving jobs for them. */ - async start() { + start() { this.txFetcher.start(); - await this.bondManager.ensureBond(); this.epochsMonitor.start(this); - this.claimsMonitor.start(this); this.log.info('Started ProverNode', this.options); } @@ -221,7 +142,6 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr this.log.info('Stopping ProverNode'); await this.txFetcher.stop(); await this.epochsMonitor.stop(); - await this.claimsMonitor.stop(); await this.prover.stop(); await tryStop(this.l2BlockSource); this.publisher.interrupt(); @@ -232,16 +152,6 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr this.log.info('Stopped ProverNode'); } - /** Sends an epoch proof quote to the coordinator. */ - public sendEpochProofQuote(quote: EpochProofQuote): Promise { - this.log.info(`Sending quote for epoch`, quote.toViemArgs().quote); - return this.doSendEpochProofQuote(quote); - } - - private doSendEpochProofQuote(quote: EpochProofQuote) { - return this.coordination.addEpochProofQuote(quote); - } - /** * Creates a proof for a block range. Returns once the proof has been submitted to L1. */ @@ -268,8 +178,22 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr /** * Returns an array of jobs being processed. */ - public getJobs(): Promise<{ uuid: string; status: EpochProvingJobState }[]> { - return Promise.resolve(Array.from(this.jobs.entries()).map(([uuid, job]) => ({ uuid, status: job.getState() }))); + public getJobs(): Promise<{ uuid: string; status: EpochProvingJobState; epochNumber: number }[]> { + return Promise.resolve( + Array.from(this.jobs.entries()).map(([uuid, job]) => ({ + uuid, + status: job.getState(), + epochNumber: Number(job.getEpochNumber()), + })), + ); + } + + protected async getActiveJobsForEpoch( + epochBigInt: bigint, + ): Promise<{ uuid: string; status: EpochProvingJobState }[]> { + const jobs = await this.getJobs(); + const epochNumber = Number(epochBigInt); + return jobs.filter(job => job.epochNumber === epochNumber && !EpochProvingJobTerminalState.includes(job.status)); } private checkMaximumPendingJobs() { @@ -398,7 +322,6 @@ export class ProverNode implements ClaimsMonitorHandler, EpochMonitorHandler, Pr /** Extracted for testing purposes. */ protected async triggerMonitors() { await this.epochsMonitor.work(); - await this.claimsMonitor.work(); } } diff --git a/yarn-project/prover-node/src/quote-provider/http.test.ts b/yarn-project/prover-node/src/quote-provider/http.test.ts deleted file mode 100644 index cdb3829ba09f..000000000000 --- a/yarn-project/prover-node/src/quote-provider/http.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { L2Block } from '@aztec/circuit-types'; -import { timesParallel } from '@aztec/foundation/collection'; -import { promiseWithResolvers } from '@aztec/foundation/promise'; - -import { type Server, createServer } from 'http'; -import { type AddressInfo } from 'net'; - -import { HttpQuoteProvider } from './http.js'; - -describe('HttpQuoteProvider', () => { - let server: Server; - let port: number; - - let status: number = 200; - let response: any = {}; - let request: any = {}; - - let provider: HttpQuoteProvider; - let blocks: L2Block[]; - - beforeAll(async () => { - server = createServer({ keepAliveTimeout: 60000 }, (req, res) => { - const chunks: Buffer[] = []; - req - .on('data', (chunk: Buffer) => { - chunks.push(chunk); - }) - .on('end', () => { - request = JSON.parse(Buffer.concat(chunks).toString()); - }); - - res.writeHead(status, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify(response)); - }); - - const { promise, resolve } = promiseWithResolvers(); - server.listen(0, '127.0.0.1', () => resolve(null)); - await promise; - port = (server.address() as AddressInfo).port; - }); - - beforeEach(async () => { - provider = new HttpQuoteProvider(`http://127.0.0.1:${port}`); - blocks = await timesParallel(3, i => L2Block.random(i + 1, 4)); - response = { basisPointFee: 100, bondAmount: '100000000000000000000', validUntilSlot: '100' }; - }); - - afterAll(() => { - server?.close(); - }); - - it('requests a quote sending epoch data', async () => { - const quote = await provider.getQuote(1, blocks); - - expect(request).toEqual( - expect.objectContaining({ epochNumber: 1, fromBlock: 1, toBlock: 3, txCount: 12, totalFees: expect.any(String) }), - ); - - expect(quote).toEqual({ - basisPointFee: response.basisPointFee, - bondAmount: BigInt(response.bondAmount), - validUntilSlot: BigInt(response.validUntilSlot), - }); - }); - - it('throws an error if the response is missing required fields', async () => { - response = { basisPointFee: 100 }; - await expect(provider.getQuote(1, blocks)).rejects.toThrow(/Missing required fields/i); - }); - - it('throws an error if the response is not ok', async () => { - status = 400; - await expect(provider.getQuote(1, blocks)).rejects.toThrow(/Failed to fetch quote/i); - }); -}); diff --git a/yarn-project/prover-node/src/quote-provider/http.ts b/yarn-project/prover-node/src/quote-provider/http.ts deleted file mode 100644 index a50318143e93..000000000000 --- a/yarn-project/prover-node/src/quote-provider/http.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { type L2Block } from '@aztec/circuit-types'; -import { jsonStringify } from '@aztec/foundation/json-rpc'; - -import { type QuoteProvider, type QuoteProviderResult } from './index.js'; -import { getTotalFees, getTxCount } from './utils.js'; - -export class HttpQuoteProvider implements QuoteProvider { - constructor(private readonly url: string) {} - - public async getQuote(epochNumber: number, epoch: L2Block[]): Promise { - const payload: HttpQuoteRequestPayload = { - epochNumber, - fromBlock: epoch[0].number, - toBlock: epoch.at(-1)!.number, - totalFees: getTotalFees(epoch).toString(), - txCount: getTxCount(epoch), - }; - - const response = await fetch(this.url, { - method: 'POST', - body: jsonStringify(payload), - headers: { 'content-type': 'application/json' }, - }); - - if (!response.ok) { - throw new Error(`Failed to fetch quote: ${response.statusText}`); - } - - const data = await response.json(); - if (!data.basisPointFee || !data.bondAmount) { - throw new Error(`Missing required fields (basisPointFee | bondAmount) in response: ${jsonStringify(data)}`); - } - - const basisPointFee = Number(data.basisPointFee); - const bondAmount = BigInt(data.bondAmount); - const validUntilSlot = data.validUntilSlot ? BigInt(data.validUntilSlot) : undefined; - - return { basisPointFee, bondAmount, validUntilSlot }; - } -} - -export type HttpQuoteRequestPayload = { - epochNumber: number; - fromBlock: number; - toBlock: number; - totalFees: string; - txCount: number; -}; diff --git a/yarn-project/prover-node/src/quote-provider/index.ts b/yarn-project/prover-node/src/quote-provider/index.ts deleted file mode 100644 index ec768900ebd7..000000000000 --- a/yarn-project/prover-node/src/quote-provider/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { type EpochProofQuotePayload, type L2Block } from '@aztec/circuit-types'; - -export type QuoteProviderResult = Pick & - Partial>; - -export interface QuoteProvider { - getQuote(epochNumber: number, epoch: L2Block[]): Promise; -} diff --git a/yarn-project/prover-node/src/quote-provider/simple.ts b/yarn-project/prover-node/src/quote-provider/simple.ts deleted file mode 100644 index bcd4cbe05c1e..000000000000 --- a/yarn-project/prover-node/src/quote-provider/simple.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { type EpochProofQuotePayload, type L2Block } from '@aztec/circuit-types'; - -import { type QuoteProvider } from './index.js'; - -export class SimpleQuoteProvider implements QuoteProvider { - constructor(public readonly basisPointFee: number, public readonly bondAmount: bigint) {} - - getQuote( - _epochNumber: number, - _epoch: L2Block[], - ): Promise> { - const { basisPointFee, bondAmount } = this; - return Promise.resolve({ basisPointFee, bondAmount }); - } -} diff --git a/yarn-project/prover-node/src/quote-provider/utils.ts b/yarn-project/prover-node/src/quote-provider/utils.ts deleted file mode 100644 index 253480476c19..000000000000 --- a/yarn-project/prover-node/src/quote-provider/utils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { type L2Block } from '@aztec/circuit-types'; -import { Fr } from '@aztec/circuits.js'; - -export function getTotalFees(epoch: L2Block[]) { - return epoch.reduce((total, block) => total.add(block.header.totalFees), Fr.ZERO).toBigInt(); -} - -export function getTxCount(epoch: L2Block[]) { - return epoch.reduce((total, block) => total + block.body.txEffects.length, 0); -} diff --git a/yarn-project/prover-node/src/quote-signer.ts b/yarn-project/prover-node/src/quote-signer.ts deleted file mode 100644 index 0c2f4bd9c4eb..000000000000 --- a/yarn-project/prover-node/src/quote-signer.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { EpochProofQuote, type EpochProofQuotePayload } from '@aztec/circuit-types'; -import { Buffer32 } from '@aztec/foundation/buffer'; -import { Secp256k1Signer } from '@aztec/foundation/crypto'; -import { type RollupAbi } from '@aztec/l1-artifacts'; - -import { type GetContractReturnType, type PublicClient } from 'viem'; - -export class QuoteSigner { - constructor( - private readonly signer: Secp256k1Signer, - private readonly quoteToDigest: (payload: EpochProofQuotePayload) => Promise, - ) {} - - static new(privateKey: Buffer32, rollupContract: GetContractReturnType): QuoteSigner { - const quoteToDigest = (payload: EpochProofQuotePayload) => - rollupContract.read.quoteToDigest([payload.toViemArgs()]).then(Buffer32.fromString); - return new QuoteSigner(new Secp256k1Signer(privateKey), quoteToDigest); - } - - public async sign(payload: EpochProofQuotePayload) { - const digest = await this.quoteToDigest(payload); - return EpochProofQuote.new(digest, payload, this.signer); - } -} diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher-metrics.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher-metrics.ts index f7bd8e96effc..f8c32f1a090c 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher-metrics.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher-metrics.ts @@ -1,4 +1,4 @@ -import type { L1PublishBlockStats, L1PublishProofStats, L1PublishStats } from '@aztec/circuit-types/stats'; +import type { L1PublishBlockStats, L1PublishStats } from '@aztec/circuit-types/stats'; import { Attributes, type Histogram, @@ -10,7 +10,7 @@ import { import { formatEther } from 'viem/utils'; -export type L1TxType = 'submitProof' | 'process' | 'claimEpochProofRight'; +export type L1TxType = 'process'; export class SequencerPublisherMetrics { private gasPrice: Histogram; @@ -109,10 +109,6 @@ export class SequencerPublisherMetrics { } } - recordSubmitProof(durationMs: number, stats: L1PublishProofStats) { - this.recordTx('submitProof', durationMs, stats); - } - recordProcessBlockTx(durationMs: number, stats: L1PublishBlockStats) { this.recordTx('process', durationMs, stats); @@ -127,10 +123,6 @@ export class SequencerPublisherMetrics { } } - recordClaimEpochProofRightTx(durationMs: number, stats: L1PublishStats) { - this.recordTx('claimEpochProofRight', durationMs, stats); - } - private recordTx(txType: L1TxType, durationMs: number, stats: L1PublishStats) { const attributes = { [Attributes.L1_TX_TYPE]: txType, diff --git a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts index 5842b1370a5d..c84c59338b24 100644 --- a/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/sequencer-publisher.ts @@ -2,13 +2,12 @@ import { Blob } from '@aztec/blob-lib'; import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client'; import { ConsensusPayload, - type EpochProofQuote, type L2Block, SignatureDomainSeparator, type TxHash, getHashedSignaturePayload, } from '@aztec/circuit-types'; -import type { L1PublishBlockStats, L1PublishStats } from '@aztec/circuit-types/stats'; +import type { L1PublishBlockStats } from '@aztec/circuit-types/stats'; import { type BlockHeader, EthAddress } from '@aztec/circuits.js'; import { type EpochCache } from '@aztec/epoch-cache'; import { @@ -65,7 +64,7 @@ export enum VoteType { type GetSlashPayloadCallBack = (slotNumber: bigint) => Promise; -type Action = 'propose' | 'claim' | 'governance-vote' | 'slashing-vote'; +type Action = 'propose' | 'governance-vote' | 'slashing-vote'; interface RequestWithExpiry { action: Action; request: L1TxRequest; @@ -105,7 +104,6 @@ export class SequencerPublisher { // Total used for full block from int_l1_pub e2e test: 1m (of which 86k is 1x blob) // Total used for emptier block from above test: 429k (of which 84k is 1x blob) public static PROPOSE_GAS_GUESS: bigint = 12_000_000n; - public static PROPOSE_AND_CLAIM_GAS_GUESS: bigint = this.PROPOSE_GAS_GUESS + 100_000n; public l1TxUtils: L1TxUtilsWithBlobs; public rollupContract: RollupContract; @@ -270,40 +268,6 @@ export class SequencerPublisher { }); } - /** - * @returns The epoch that is currently claimable, undefined otherwise - */ - public getClaimableEpoch() { - const acceptedErrors = ['Rollup__NoEpochToProve', 'Rollup__ProofRightAlreadyClaimed'] as const; - return this.rollupContract.getClaimableEpoch().catch(err => { - if (acceptedErrors.find(e => err.message.includes(e))) { - return undefined; - } - throw err; - }); - } - - /** - * @notice Will filter out invalid quotes according to L1 - * @param quotes - The quotes to filter - * @returns The filtered quotes - */ - public filterValidQuotes(quotes: EpochProofQuote[]): Promise { - return Promise.all( - quotes.map(x => - this.rollupContract - // validate throws if the quote is not valid - // else returns void - .validateProofQuote(x.toViemArgs(), this.getForwarderAddress().toString(), this.ethereumSlotDuration) - .then(() => x) - .catch(err => { - this.log.error(`Failed to validate proof quote`, err, { quote: x.toInspect() }); - return undefined; - }), - ), - ).then(quotes => quotes.filter((q): q is EpochProofQuote => !!q)); - } - /** * @notice Will call `validateHeader` to make sure that it is possible to propose * @@ -463,52 +427,6 @@ export class SequencerPublisher { return true; } - /** Enqueues a claimEpochProofRight transaction to submit a chosen prover quote for the previous epoch. */ - public enqueueClaimEpochProofRight(proofQuote: EpochProofQuote): boolean { - const timer = new Timer(); - this.addRequest({ - action: 'claim', - request: { - to: this.rollupContract.address, - data: encodeFunctionData({ - abi: RollupAbi, - functionName: 'claimEpochProofRight', - args: [proofQuote.toViemArgs()], - }), - }, - lastValidL2Slot: this.getCurrentL2Slot(), - onResult: (_request, result) => { - if (!result) { - return; - } - const { receipt, stats } = result; - if (receipt.status === 'success') { - const publishStats: L1PublishStats = { - gasPrice: receipt.effectiveGasPrice, - gasUsed: receipt.gasUsed, - transactionHash: receipt.transactionHash, - blobDataGas: 0n, - blobGasUsed: 0n, - ...pick(stats!, 'calldataGas', 'calldataSize', 'sender'), - }; - this.log.verbose(`Submitted claim epoch proof right to L1 rollup contract`, { - ...publishStats, - ...proofQuote.toInspect(), - }); - this.metrics.recordClaimEpochProofRightTx(timer.ms(), publishStats); - } else { - this.metrics.recordFailedTx('claimEpochProofRight'); - // TODO: Get the error message from the reverted tx - this.log.error(`Claim epoch proof right tx reverted`, { - txHash: receipt.transactionHash, - ...proofQuote.toInspect(), - }); - } - }, - }); - return true; - } - /** * Calling `interrupt` will cause any in progress call to `publishRollup` to return `false` asap. * Be warned, the call may return false even if the tx subsequently gets successfully mined. diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 4bfd14aeed03..1240fa980dfd 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -14,7 +14,6 @@ import { TxHash, WorldStateRunningState, type WorldStateSynchronizer, - mockEpochProofQuote as baseMockEpochProofQuote, makeProcessedTxFromPrivateOnlyTx, mockTxForRollup, } from '@aztec/circuit-types'; @@ -77,11 +76,7 @@ describe('sequencer', () => { let sequencer: TestSubject; - const { - aztecEpochDuration: epochDuration, - aztecSlotDuration: slotDuration, - ethereumSlotDuration, - } = DefaultL1ContractsConfig; + const { aztecSlotDuration: slotDuration, ethereumSlotDuration } = DefaultL1ContractsConfig; const chainId = new Fr(12345); const version = Fr.ZERO; @@ -173,7 +168,6 @@ describe('sequencer', () => { publisher.validateBlockForSubmission.mockResolvedValue(1n); publisher.enqueueProposeL2Block.mockResolvedValue(true); publisher.enqueueCastVote.mockResolvedValue(true); - publisher.enqueueClaimEpochProofRight.mockReturnValue(true); publisher.canProposeAtNextEthBlock.mockResolvedValue([BigInt(newSlotNumber), BigInt(newBlockNumber)]); globalVariableBuilder = mock(); @@ -565,223 +559,6 @@ describe('sequencer', () => { // Even though the block publish was not enqueued, we still send any requests expect(publisher.sendRequests).toHaveBeenCalledTimes(1); }); - - describe('proof quotes', () => { - let tx: Tx; - let txHash: TxHash; - let currentEpoch = 0n; - - const setupForBlockNumber = async (blockNumber: number) => { - newBlockNumber = blockNumber; - newSlotNumber = blockNumber; - currentEpoch = BigInt(blockNumber) / BigInt(epochDuration); - - globalVariables = new GlobalVariables( - chainId, - version, - new Fr(blockNumber), - new Fr(blockNumber), - Fr.ZERO, - coinbase, - feeRecipient, - gasFees, - ); - - worldState.status.mockResolvedValue({ - state: WorldStateRunningState.IDLE, - syncedToL2Block: { number: blockNumber - 1, hash }, - }); - - p2p.getStatus.mockResolvedValue({ - state: P2PClientState.IDLE, - syncedToL2Block: { number: blockNumber - 1, hash }, - }); - - l2BlockSource.getBlock.mockResolvedValue(L2Block.random(blockNumber - 1)); - l2BlockSource.getBlockNumber.mockResolvedValue(blockNumber - 1); - l2BlockSource.getL2Tips.mockResolvedValue({ - latest: { number: blockNumber - 1, hash }, - proven: { number: 0, hash: undefined }, - finalized: { number: 0, hash: undefined }, - }); - - l1ToL2MessageSource.getBlockNumber.mockResolvedValue(blockNumber - 1); - - globalVariableBuilder.buildGlobalVariables.mockResolvedValue(globalVariables); - - publisher.enqueueClaimEpochProofRight.mockReturnValueOnce(true); - publisher.canProposeAtNextEthBlock.mockResolvedValue([BigInt(newSlotNumber), BigInt(blockNumber)]); - - tx = await makeTx(); - txHash = await tx.getTxHash(); - - mockPendingTxs([tx]); - block = await makeBlock([tx]); - }; - - const mockEpochProofQuote = (opts: { epoch?: bigint; validUntilSlot?: bigint; fee?: number } = {}) => - baseMockEpochProofQuote( - opts.epoch ?? currentEpoch - 1n, - opts.validUntilSlot ?? block.header.globalVariables.slotNumber.toBigInt() + 1n, - 10000n, - EthAddress.random(), - opts.fee ?? 1, - ); - - it('submits a valid proof quote with a block', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - const proofQuote = mockEpochProofQuote(); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - await sequencer.doRealWork(); - expect(publisher.enqueueClaimEpochProofRight).toHaveBeenCalledWith(proofQuote); - expectPublisherProposeL2Block([txHash]); - }); - - it('submits a valid proof quote even without a block', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - // There are no txs! - mockPendingTxs([]); - - const proofQuote = mockEpochProofQuote(); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - await sequencer.doRealWork(); - expect(publisher.enqueueClaimEpochProofRight).toHaveBeenCalledWith(proofQuote); - expect(publisher.enqueueProposeL2Block).not.toHaveBeenCalled(); - }); - - it('submits a valid proof quote if building a block proposal fails', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - const proofQuote = mockEpochProofQuote(); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - validatorClient.createBlockProposal.mockResolvedValue(undefined); - - await sequencer.doRealWork(); - expect(publisher.enqueueClaimEpochProofRight).toHaveBeenCalledWith(proofQuote); - expect(publisher.enqueueProposeL2Block).not.toHaveBeenCalled(); - }); - - it('does not claim the epoch previous to the first', async () => { - const blockNumber = 1; - await setupForBlockNumber(blockNumber); - - const proofQuote = mockEpochProofQuote({ epoch: 0n }); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(undefined)); - - await sequencer.doRealWork(); - expectPublisherProposeL2Block([txHash]); - }); - - it('does not submit a quote with an expired slot number', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - const expiredSlotNumber = block.header.globalVariables.slotNumber.toBigInt() - 1n; - const proofQuote = mockEpochProofQuote({ validUntilSlot: expiredSlotNumber }); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - await sequencer.doRealWork(); - expectPublisherProposeL2Block([txHash]); - }); - - it('does not submit a valid quote if unable to claim epoch', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - const proofQuote = mockEpochProofQuote(); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve([proofQuote])); - - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(undefined)); - - await sequencer.doRealWork(); - expectPublisherProposeL2Block([txHash]); - }); - - it('does not submit an invalid quote', async () => { - const blockNumber = epochDuration + 1; - await setupForBlockNumber(blockNumber); - - const proofQuote = mockEpochProofQuote(); - - p2p.getEpochProofQuotes.mockResolvedValue([proofQuote]); - publisher.enqueueProposeL2Block.mockResolvedValueOnce(true); - - // Quote is reported as invalid - publisher.filterValidQuotes.mockImplementation(() => Promise.reject(new Error('Invalid proof quote'))); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - await sequencer.doRealWork(); - expectPublisherProposeL2Block([txHash]); - }); - - it('selects the lowest cost valid quote', async () => { - const blockNumber = epochDuration + 1; - await 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({ fee: 10 + i })); - - const expiredSlot = block.header.globalVariables.slotNumber.toBigInt() - 1n; - const proofQuoteInvalidSlot = mockEpochProofQuote({ validUntilSlot: expiredSlot, fee: 1 }); - const proofQuoteInvalidEpoch = mockEpochProofQuote({ epoch: currentEpoch, fee: 2 }); - - // This is deemed invalid by the contract, we identify it by its fee - const proofQuoteInvalid = mockEpochProofQuote({ fee: 3 }); - - const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes, proofQuoteInvalid]; - - p2p.getEpochProofQuotes.mockResolvedValue(allQuotes); - publisher.enqueueProposeL2Block.mockResolvedValueOnce(true); - - // Quote is reported as invalid - publisher.filterValidQuotes.mockImplementation(() => Promise.resolve(validQuotes)); - - // The previous epoch can be claimed - publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n)); - - await sequencer.doRealWork(); - expect(publisher.enqueueClaimEpochProofRight).toHaveBeenCalledWith(validQuotes[0]); - expectPublisherProposeL2Block([txHash]); - }); - }); }); 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 7c7253f3b8e3..58dd8d97cc8d 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,5 +1,4 @@ import { - type EpochProofQuote, type L1RollupConstants, type L1ToL2MessageSource, type L2Block, @@ -9,7 +8,7 @@ import { type TxHash, type WorldStateSynchronizer, } from '@aztec/circuit-types'; -import type { AllowedElement, Signature, WorldStateSynchronizerStatus } from '@aztec/circuit-types/interfaces'; +import type { AllowedElement, WorldStateSynchronizerStatus } from '@aztec/circuit-types/interfaces'; import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats'; import { AppendOnlyTreeSnapshot, @@ -25,6 +24,7 @@ import { import { AztecAddress } from '@aztec/foundation/aztec-address'; import { omit } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; +import type { Signature } from '@aztec/foundation/eth-signature'; import { Fr } from '@aztec/foundation/fields'; import { createLogger } from '@aztec/foundation/log'; import { RunningPromise } from '@aztec/foundation/running-promise'; @@ -272,9 +272,6 @@ export class Sequencer { VoteType.SLASHING, ); - // Start collecting proof quotes for the previous epoch if needed in the background - const createProofQuotePromise = this.createProofClaimForPreviousEpoch(slot); - this.setState(SequencerState.INITIALIZING_PROPOSAL, slot); this.log.verbose(`Preparing proposal for block ${newBlockNumber} at slot ${slot}`, { chainTipArchive, @@ -315,11 +312,6 @@ export class Sequencer { await enqueueSlashingVotePromise.catch(err => { this.log.error(`Error enqueuing slashing vote`, err, { blockNumber: newBlockNumber, slot }); }); - await createProofQuotePromise - .then(quote => (quote ? this.publisher.enqueueClaimEpochProofRight(quote) : undefined)) - .catch(err => { - this.log.error(`Error creating proof quote`, err, { blockNumber: newBlockNumber, slot }); - }); await this.publisher.sendRequests(); @@ -658,54 +650,6 @@ export class Sequencer { return orderAttestations(attestations, committee); } - protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise { - try { - // Find out which epoch we are currently in - const epochToProve = await this.publisher.getClaimableEpoch(); - - if (epochToProve === undefined) { - this.log.trace(`No epoch to claim at slot ${slotNumber}`); - return undefined; - } - - // Get quotes for the epoch to be proven - this.log.debug(`Collecting proof quotes for epoch ${epochToProve}`); - const p2pQuotes = await this.p2pClient - .getEpochProofQuotes(epochToProve) - .then(quotes => - quotes - .filter(x => x.payload.validUntilSlot >= slotNumber) - .filter(x => x.payload.epochToProve === epochToProve), - ); - this.log.verbose(`Retrieved ${p2pQuotes.length} quotes for slot ${slotNumber} epoch ${epochToProve}`, { - epochToProve, - slotNumber, - quotes: p2pQuotes.map(q => q.payload), - }); - if (!p2pQuotes.length) { - return undefined; - } - - // ensure these quotes are still valid for the slot and have the contract validate them - const validQuotes = await this.publisher.filterValidQuotes(p2pQuotes); - - if (!validQuotes.length) { - this.log.warn(`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, - ); - const quote = sortedQuotes[0]; - this.log.info(`Selected proof quote for proof claim`, { quote: quote.toInspect() }); - return quote; - } catch (err) { - this.log.error(`Failed to create proof claim for previous epoch`, err, { slotNumber }); - return undefined; - } - } - /** * Publishes the L2Block to the rollup contract. * @param block - The L2Block to be published. @@ -734,29 +678,6 @@ export class Sequencer { } } - @trackSpan( - 'Sequencer.claimEpochProofRightIfAvailable', - slotNumber => ({ [Attributes.SLOT_NUMBER]: Number(slotNumber) }), - epoch => ({ [Attributes.EPOCH_NUMBER]: Number(epoch) }), - ) - /** Collects an epoch proof quote if there is an epoch to prove, and submits it to the L1 contract. */ - protected async claimEpochProofRightIfAvailable(slotNumber: bigint) { - const proofQuote = await this.createProofClaimForPreviousEpoch(slotNumber); - if (proofQuote === undefined) { - return; - } - - const epoch = proofQuote.payload.epochToProve; - const ctx = { slotNumber, epoch, quote: proofQuote.toInspect() }; - this.log.verbose(`Claiming proof right for epoch ${epoch}`, ctx); - const enqueued = this.publisher.enqueueClaimEpochProofRight(proofQuote); - if (!enqueued) { - throw new Error(`Failed to enqueue claim of proof right for epoch ${epoch}`); - } - this.log.info(`Enqueued claim of proof right for epoch ${epoch}`, ctx); - return epoch; - } - /** * Returns whether all dependencies have caught up. * We don't check against the previous block submitted since it may have been reorg'd out. diff --git a/yarn-project/sequencer-client/src/sequencer/utils.ts b/yarn-project/sequencer-client/src/sequencer/utils.ts index c4253c4cf0fe..11552735b585 100644 --- a/yarn-project/sequencer-client/src/sequencer/utils.ts +++ b/yarn-project/sequencer-client/src/sequencer/utils.ts @@ -1,4 +1,5 @@ -import type { BlockAttestation, EthAddress } from '@aztec/circuit-types'; +import type { BlockAttestation } from '@aztec/circuit-types'; +import { type EthAddress } from '@aztec/foundation/eth-address'; import { Signature } from '@aztec/foundation/eth-signature'; export enum SequencerState { diff --git a/yarn-project/telemetry-client/src/metrics.ts b/yarn-project/telemetry-client/src/metrics.ts index a0521bec8913..d60da7575c92 100644 --- a/yarn-project/telemetry-client/src/metrics.ts +++ b/yarn-project/telemetry-client/src/metrics.ts @@ -35,9 +35,6 @@ export const DB_USED_SIZE = 'aztec.db.used_size'; export const MEMPOOL_ATTESTATIONS_COUNT = 'aztec.mempool.attestations_count'; export const MEMPOOL_ATTESTATIONS_SIZE = 'aztec.mempool.attestations_size'; -export const MEMPOOL_PROVER_QUOTE_COUNT = 'aztec.mempool.prover_quote_count'; -export const MEMPOOL_PROVER_QUOTE_SIZE = 'aztec.mempool.prover_quote_size'; - export const ARCHIVER_SYNC_DURATION = 'aztec.archiver.sync_duration'; export const ARCHIVER_L1_BLOCKS_SYNCED = 'aztec.archiver.l1_blocks_synced'; export const ARCHIVER_BLOCK_HEIGHT = 'aztec.archiver.block_height'; diff --git a/yarn-project/txe/src/node/txe_node.ts b/yarn-project/txe/src/node/txe_node.ts index 39bf2d1e4cd8..669dd958d004 100644 --- a/yarn-project/txe/src/node/txe_node.ts +++ b/yarn-project/txe/src/node/txe_node.ts @@ -1,7 +1,6 @@ import { createLogger } from '@aztec/aztec.js'; import { type AztecNode, - type EpochProofQuote, type GetContractClassLogsResponse, type GetPublicLogsResponse, type InBlock, @@ -651,22 +650,6 @@ export class TXENode implements AztecNode { throw new Error('TXE Node method getEncodedEnr not implemented'); } - /** - * Receives a quote for an epoch proof and stores it in its EpochProofQuotePool - * @param quote - The quote to store - */ - addEpochProofQuote(_quote: EpochProofQuote): Promise { - throw new Error('TXE Node method addEpochProofQuote not implemented'); - } - - /** - * Returns the received quotes for a given epoch - * @param epoch - The epoch for which to get the quotes - */ - getEpochProofQuotes(_epoch: bigint): Promise { - throw new Error('TXE Node method getEpochProofQuotes not implemented'); - } - /** * Adds a contract class bypassing the registerer. * TODO(#10007): Remove this method.