diff --git a/l1-contracts/src/core/Rollup.sol b/l1-contracts/src/core/Rollup.sol index 17fc8173742..d6f8b2c8577 100644 --- a/l1-contracts/src/core/Rollup.sol +++ b/l1-contracts/src/core/Rollup.sol @@ -227,9 +227,18 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { bytes calldata _aggregationObject, bytes calldata _proof ) external override(IRollup) { + if (canPrune()) { + _prune(); + } + uint256 previousBlockNumber = tips.provenBlockNumber; uint256 endBlockNumber = previousBlockNumber + _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. + Epoch epochToProve = getEpochForBlock(endBlockNumber); + bytes32[] memory publicInputs = getEpochProofPublicInputs(_epochSize, _args, _fees, _aggregationObject); @@ -287,7 +296,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { } } - if (proofClaim.epochToProve == getEpochForBlock(endBlockNumber)) { + if (proofClaim.epochToProve == epochToProve) { PROOF_COMMITMENT_ESCROW.unstakeBond(proofClaim.bondProvider, proofClaim.bondAmount); } @@ -336,7 +345,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { // Consider if a prune will hit in this slot uint256 pendingBlockNumber = - _canPruneAtTime(_ts) ? tips.provenBlockNumber : tips.pendingBlockNumber; + canPruneAtTime(_ts) ? tips.provenBlockNumber : tips.pendingBlockNumber; Slot lastSlot = blocks[pendingBlockNumber].slotNumber; @@ -772,25 +781,10 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { } function canPrune() public view override(IRollup) returns (bool) { - return _canPruneAtTime(Timestamp.wrap(block.timestamp)); + return canPruneAtTime(Timestamp.wrap(block.timestamp)); } - function _prune() internal { - // TODO #8656 - delete proofClaim; - - uint256 pending = tips.pendingBlockNumber; - - // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. - // We can do because any new block proposed will overwrite a previous block in the block log, - // so no values should "survive". - // People must therefore read the chain using the pendingTip as a boundary. - tips.pendingBlockNumber = tips.provenBlockNumber; - - emit PrunedPending(tips.provenBlockNumber, pending); - } - - function _canPruneAtTime(Timestamp _ts) internal view returns (bool) { + function canPruneAtTime(Timestamp _ts) public view override(IRollup) returns (bool) { if ( tips.pendingBlockNumber == tips.provenBlockNumber || tips.pendingBlockNumber <= assumeProvenThroughBlockNumber @@ -819,6 +813,21 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { return true; } + function _prune() internal { + // TODO #8656 + delete proofClaim; + + uint256 pending = tips.pendingBlockNumber; + + // @note We are not deleting the blocks, but we are "winding back" the pendingTip to the last block that was proven. + // We can do because any new block proposed will overwrite a previous block in the block log, + // so no values should "survive". + // People must therefore read the chain using the pendingTip as a boundary. + tips.pendingBlockNumber = tips.provenBlockNumber; + + emit PrunedPending(tips.provenBlockNumber, pending); + } + /** * @notice Validates the header for submission * @@ -838,7 +847,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup { DataStructures.ExecutionFlags memory _flags ) internal view { uint256 pendingBlockNumber = - _canPruneAtTime(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber; + canPruneAtTime(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber; _validateHeaderForSubmissionBase( _header, _currentTime, _txEffectsHash, pendingBlockNumber, _flags ); diff --git a/l1-contracts/src/core/interfaces/IRollup.sol b/l1-contracts/src/core/interfaces/IRollup.sol index cfb1f310081..088762b56d0 100644 --- a/l1-contracts/src/core/interfaces/IRollup.sol +++ b/l1-contracts/src/core/interfaces/IRollup.sol @@ -101,6 +101,7 @@ interface IRollup { function archive() external view returns (bytes32); function archiveAt(uint256 _blockNumber) external view returns (bytes32); function canPrune() external view returns (bool); + function canPruneAtTime(Timestamp _ts) external view returns (bool); function getProvenBlockNumber() external view returns (uint256); function getPendingBlockNumber() external view returns (uint256); function getEpochToProve() external view returns (Epoch); diff --git a/l1-contracts/test/Rollup.t.sol b/l1-contracts/test/Rollup.t.sol index c7b728bea6a..5ed5e5b2d70 100644 --- a/l1-contracts/test/Rollup.t.sol +++ b/l1-contracts/test/Rollup.t.sol @@ -418,9 +418,7 @@ contract RollupTest is DecoderBase { (bytes32 preArchive, bytes32 preBlockHash,) = rollup.blocks(0); _submitEpochProof(rollup, 1, preArchive, archive, preBlockHash, blockHash, proverId); - vm.expectRevert( - abi.encodeWithSelector(Errors.Rollup__InvalidPreviousArchive.selector, archive, preArchive) - ); + vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlockNumber.selector, 1, 2)); _submitEpochProof(rollup, 1, preArchive, archive, preBlockHash, blockHash, proverId); } diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index dc6fc63e756..54fa9029348 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -6,7 +6,7 @@ import { LogType, UnencryptedL2BlockL2Logs, } from '@aztec/circuit-types'; -import { GENESIS_ARCHIVE_ROOT } from '@aztec/circuits.js'; +import { ETHEREUM_SLOT_DURATION, GENESIS_ARCHIVE_ROOT } from '@aztec/circuits.js'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; import { sleep } from '@aztec/foundation/sleep'; @@ -60,7 +60,7 @@ describe('Archiver', () => { now = +new Date(); publicClient = mock>({ getBlock: ((args: any) => ({ - timestamp: args.blockNumber * 1000n + BigInt(now), + timestamp: args.blockNumber * BigInt(ETHEREUM_SLOT_DURATION) + BigInt(now), })) as any, }); @@ -98,7 +98,7 @@ describe('Archiver', () => { let latestBlockNum = await archiver.getBlockNumber(); expect(latestBlockNum).toEqual(0); - blocks.forEach((b, i) => (b.header.globalVariables.timestamp = new Fr(now + 1000 * (i + 1)))); + blocks.forEach((b, i) => (b.header.globalVariables.timestamp = new Fr(now + ETHEREUM_SLOT_DURATION * (i + 1)))); const rollupTxs = blocks.map(makeRollupTx); publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2600n).mockResolvedValueOnce(2700n); diff --git a/yarn-project/archiver/src/archiver/archiver.ts b/yarn-project/archiver/src/archiver/archiver.ts index a205a5d3881..5a12a9ef872 100644 --- a/yarn-project/archiver/src/archiver/archiver.ts +++ b/yarn-project/archiver/src/archiver/archiver.ts @@ -23,6 +23,7 @@ import { type ContractDataSource, ContractInstanceDeployedEvent, type ContractInstanceWithAddress, + ETHEREUM_SLOT_DURATION, type ExecutablePrivateFunctionWithMembershipProof, type FunctionSelector, type Header, @@ -246,6 +247,12 @@ export class Archiver implements ArchiveSource { // ********** Events that are processed per L1 block ********** await this.handleL1ToL2Messages(blockUntilSynced, messagesSynchedTo, currentL1BlockNumber); + // Store latest l1 block number and timestamp seen. Used for epoch and slots calculations. + if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) { + this.l1Timestamp = (await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber })).timestamp; + this.l1BlockNumber = currentL1BlockNumber; + } + // ********** Events that are processed per L2 block ********** if (currentL1BlockNumber > blocksSynchedTo) { // First we retrieve new L2 blocks @@ -257,21 +264,17 @@ export class Archiver implements ArchiveSource { // up to which point we're pruning, and then requesting L2 blocks up to that point only. await this.handleEpochPrune(provenBlockNumber, currentL1BlockNumber); } - - // Store latest l1 block number and timestamp seen. Used for epoch and slots calculations. - if (!this.l1BlockNumber || this.l1BlockNumber < currentL1BlockNumber) { - this.l1Timestamp = await this.publicClient.getBlock({ blockNumber: currentL1BlockNumber }).then(b => b.timestamp); - this.l1BlockNumber = currentL1BlockNumber; - } } /** Checks if there'd be a reorg for the next block submission and start pruning now. */ private async handleEpochPrune(provenBlockNumber: bigint, currentL1BlockNumber: bigint) { const localPendingBlockNumber = BigInt(await this.getBlockNumber()); + const time = (this.l1Timestamp ?? 0n) + BigInt(ETHEREUM_SLOT_DURATION); + const canPrune = localPendingBlockNumber > provenBlockNumber && - (await this.rollup.read.canPrune({ blockNumber: currentL1BlockNumber })); + (await this.rollup.read.canPruneAtTime([time], { blockNumber: currentL1BlockNumber })); if (canPrune) { this.log.verbose(`L2 prune will occur on next submission. Rolling back to last proven block.`);