Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add canPruneAtTime #9751

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 29 additions & 20 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*
Expand All @@ -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
);
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 10 additions & 10 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -266,16 +266,18 @@ contract RollupTest is DecoderBase {
);
}

function testMissingProofSlashesBond(uint256 slotsToJump) public setUpFor("mixed_block_1") {
// @note, this gives a an overflow if bounding to type(uint256).max
// Tracked by https://github.com/AztecProtocol/aztec-packages/issues/9362
slotsToJump =
bound(slotsToJump, 2 * Constants.AZTEC_EPOCH_DURATION, 1e20 * Constants.AZTEC_EPOCH_DURATION);
function testMissingProofSlashesBond(uint256 _slotToHit) public setUpFor("mixed_block_1") {
Slot lower = rollup.getCurrentSlot() + Slot.wrap(2 * Constants.AZTEC_EPOCH_DURATION);
Slot upper = Slot.wrap(
(type(uint256).max - Timestamp.unwrap(rollup.GENESIS_TIME())) / rollup.SLOT_DURATION()
);
Slot slotToHit = Slot.wrap(bound(_slotToHit, lower.unwrap(), upper.unwrap()));

_testBlock("mixed_block_1", false, 1);
rollup.claimEpochProofRight(signedQuote);
warpToL2Slot(slotsToJump);
warpToL2Slot(slotToHit.unwrap());
rollup.prune();
_testBlock("mixed_block_1", true, slotsToJump);
_testBlock("mixed_block_1", true, slotToHit.unwrap());

assertEq(
proofCommitmentEscrow.deposits(quote.prover), 9 * quote.bondAmount, "Invalid escrow balance"
Expand Down Expand Up @@ -416,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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,24 +52,20 @@ contract PushProposalTest is GovernanceProposerBase {
_;
}

function test_WhenRoundTooFarInPast(uint256 _slotsToJump)
function test_WhenRoundTooFarInPast(uint256 _slotToHit)
external
givenCanonicalInstanceHoldCode
whenRoundInPast
{
// it revert

uint256 lower = Timestamp.unwrap(
leonidas.getTimestampForSlot(
leonidas.getCurrentSlot()
+ Slot.wrap(governanceProposer.M() * governanceProposer.LIFETIME_IN_ROUNDS() + 1)
)
Slot lower = leonidas.getCurrentSlot()
+ Slot.wrap(governanceProposer.M() * governanceProposer.LIFETIME_IN_ROUNDS() + 1);
Slot upper = Slot.wrap(
(type(uint256).max - Timestamp.unwrap(leonidas.GENESIS_TIME())) / leonidas.SLOT_DURATION()
);
uint256 upper =
(type(uint256).max - Timestamp.unwrap(leonidas.GENESIS_TIME())) / leonidas.SLOT_DURATION();
uint256 time = bound(_slotsToJump, lower, upper);

vm.warp(time);
Slot slotToHit = Slot.wrap(bound(_slotToHit, lower.unwrap(), upper.unwrap()));
vm.warp(Timestamp.unwrap(leonidas.getTimestampForSlot(slotToHit)));

vm.expectRevert(
abi.encodeWithSelector(
Expand Down
6 changes: 3 additions & 3 deletions yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -60,7 +60,7 @@ describe('Archiver', () => {
now = +new Date();
publicClient = mock<PublicClient<HttpTransport, Chain>>({
getBlock: ((args: any) => ({
timestamp: args.blockNumber * 1000n + BigInt(now),
timestamp: args.blockNumber * BigInt(ETHEREUM_SLOT_DURATION) + BigInt(now),
})) as any,
});

Expand Down Expand Up @@ -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);
Expand Down
17 changes: 10 additions & 7 deletions yarn-project/archiver/src/archiver/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
type ContractDataSource,
ContractInstanceDeployedEvent,
type ContractInstanceWithAddress,
ETHEREUM_SLOT_DURATION,
type ExecutablePrivateFunctionWithMembershipProof,
type FunctionSelector,
type Header,
Expand Down Expand Up @@ -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
Expand All @@ -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.`);
Expand Down
Loading