Skip to content

Commit

Permalink
feat: simulate validateEpochProofQuoteHeader in the future (#9641)
Browse files Browse the repository at this point in the history
This is important because by default the simulation uses the time of the
previous block.

So without this the proposer in slot `n` will simulate but be told they
cannot claim because they were not the proposer in slot `n-1`.
  • Loading branch information
just-mitch authored Nov 1, 2024
1 parent 325bdb0 commit 284c8f8
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 71 deletions.
96 changes: 48 additions & 48 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,15 +121,6 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
setupEpoch();
}

function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory quote)
public
view
override(IRollup)
returns (bytes32)
{
return _hashTypedDataV4(EpochProofQuoteLib.hash(quote));
}

/**
* @notice Prune the pending chain up to the last proven block
*
Expand All @@ -153,25 +144,6 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
assumeProvenThroughBlockNumber = blockNumber;
}

function fakeBlockNumberAsProven(uint256 blockNumber) private {
if (blockNumber > tips.provenBlockNumber && blockNumber <= tips.pendingBlockNumber) {
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(proofClaim.epochToProve)) {
proofClaim = DataStructures.EpochProofClaim({
epochToProve: epoch,
basisPointFee: 0,
bondAmount: 0,
bondProvider: address(0),
proposerClaimant: msg.sender
});
}
}
}

/**
* @notice Set the verifier contract
*
Expand Down Expand Up @@ -367,7 +339,8 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
Slot slot = getSlotAt(_ts);

// Consider if a prune will hit in this slot
uint256 pendingBlockNumber = _canPruneAt(_ts) ? tips.provenBlockNumber : tips.pendingBlockNumber;
uint256 pendingBlockNumber =
_canPruneAtTime(_ts) ? tips.provenBlockNumber : tips.pendingBlockNumber;

Slot lastSlot = blocks[pendingBlockNumber].slotNumber;

Expand Down Expand Up @@ -441,7 +414,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
public
override(IRollup)
{
validateEpochProofRightClaim(_quote);
validateEpochProofRightClaimAtTime(Timestamp.wrap(block.timestamp), _quote);

Slot currentSlot = getCurrentSlot();
Epoch epochToProve = getEpochToProve();
Expand Down Expand Up @@ -545,6 +518,15 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
}
}

function quoteToDigest(EpochProofQuoteLib.EpochProofQuote memory quote)
public
view
override(IRollup)
returns (bytes32)
{
return _hashTypedDataV4(EpochProofQuoteLib.hash(quote));
}

/**
* @notice Returns the computed public inputs for the given epoch proof.
*
Expand Down Expand Up @@ -684,17 +666,21 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
return publicInputs;
}

function validateEpochProofRightClaim(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote)
public
view
override(IRollup)
{
function validateEpochProofRightClaimAtTime(
Timestamp _ts,
EpochProofQuoteLib.SignedEpochProofQuote calldata _quote
) public view override(IRollup) {
SignatureLib.verify(_quote.signature, _quote.quote.prover, quoteToDigest(_quote.quote));

Slot currentSlot = getCurrentSlot();
address currentProposer = getCurrentProposer();
Slot currentSlot = getSlotAt(_ts);
address currentProposer = getProposerAt(_ts);
Epoch epochToProve = getEpochToProve();

require(
_quote.quote.validUntilSlot >= currentSlot,
Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot)
);

require(
_quote.quote.basisPointFee <= 10_000,
Errors.Rollup__InvalidBasisPointFee(_quote.quote.basisPointFee)
Expand Down Expand Up @@ -734,11 +720,6 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
_quote.quote.bondAmount <= availableFundsInEscrow,
Errors.Rollup__InsufficientFundsInEscrow(_quote.quote.bondAmount, availableFundsInEscrow)
);

require(
_quote.quote.validUntilSlot >= currentSlot,
Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot)
);
}

/**
Expand Down Expand Up @@ -794,6 +775,10 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
return bytes32(0);
}

function canPrune() public view override(IRollup) returns (bool) {
return _canPruneAtTime(Timestamp.wrap(block.timestamp));
}

function _prune() internal {
// TODO #8656
delete proofClaim;
Expand All @@ -809,11 +794,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
emit PrunedPending(tips.provenBlockNumber, pending);
}

function canPrune() public view returns (bool) {
return _canPruneAt(Timestamp.wrap(block.timestamp));
}

function _canPruneAt(Timestamp _ts) internal view returns (bool) {
function _canPruneAtTime(Timestamp _ts) internal view returns (bool) {
if (
tips.pendingBlockNumber == tips.provenBlockNumber
|| tips.pendingBlockNumber <= assumeProvenThroughBlockNumber
Expand Down Expand Up @@ -861,7 +842,7 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
DataStructures.ExecutionFlags memory _flags
) internal view {
uint256 pendingBlockNumber =
_canPruneAt(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber;
_canPruneAtTime(_currentTime) ? tips.provenBlockNumber : tips.pendingBlockNumber;
_validateHeaderForSubmissionBase(
_header, _currentTime, _txEffectsHash, pendingBlockNumber, _flags
);
Expand Down Expand Up @@ -986,4 +967,23 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
require(_header.globalVariables.gasFees.feePerL2Gas == 0, Errors.Rollup__NonZeroL2Fee());
}
}

function fakeBlockNumberAsProven(uint256 blockNumber) private {
if (blockNumber > tips.provenBlockNumber && blockNumber <= tips.pendingBlockNumber) {
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(proofClaim.epochToProve)) {
proofClaim = DataStructures.EpochProofClaim({
epochToProve: epoch,
basisPointFee: 0,
bondAmount: 0,
bondProvider: address(0),
proposerClaimant: msg.sender
});
}
}
}
}
10 changes: 5 additions & 5 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ interface IRollup {

function prune() external;

function canPrune() external view returns (bool);

function claimEpochProofRight(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote) external;

function propose(
Expand Down Expand Up @@ -102,14 +100,16 @@ interface IRollup {

function archive() external view returns (bytes32);
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
function canPrune() external view returns (bool);
function getProvenBlockNumber() external view returns (uint256);
function getPendingBlockNumber() external view returns (uint256);
function getEpochToProve() external view returns (Epoch);
function getClaimableEpoch() external view returns (Epoch);
function getEpochForBlock(uint256 blockNumber) external view returns (Epoch);
function validateEpochProofRightClaim(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote)
external
view;
function validateEpochProofRightClaimAtTime(
Timestamp _ts,
EpochProofQuoteLib.SignedEpochProofQuote calldata _quote
) external view;
function getEpochProofPublicInputs(
uint256 _epochSize,
bytes32[7] calldata _args,
Expand Down
48 changes: 34 additions & 14 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ contract RollupTest is DecoderBase {
vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(_slot))));
}

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));
Expand Down Expand Up @@ -266,6 +283,8 @@ contract RollupTest is DecoderBase {

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);

Expand All @@ -291,7 +310,8 @@ contract RollupTest is DecoderBase {

function testClaimOutsideClaimPhase() public setUpFor("mixed_block_1") {
_testBlock("mixed_block_1", false, 1);

quote.validUntilSlot = Epoch.wrap(1e9).toSlots();
signedQuote = _quoteToSignedQuote(quote);
warpToL2Slot(Constants.AZTEC_EPOCH_DURATION + rollup.CLAIM_DURATION_IN_L2_SLOTS());

vm.expectRevert(
Expand Down Expand Up @@ -840,19 +860,6 @@ contract RollupTest is DecoderBase {
_submitEpochProof(rollup, 1, preArchive, data.archive, preBlockHash, wrongBlockHash, bytes32(0));
}

function _quoteToSignedQuote(EpochProofQuoteLib.EpochProofQuote memory _quote)
internal
view
returns (EpochProofQuoteLib.SignedEpochProofQuote memory)
{
bytes32 digest = rollup.quoteToDigest(_quote);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return EpochProofQuoteLib.SignedEpochProofQuote({
quote: _quote,
signature: SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s})
});
}

function _testBlock(string memory name, bool _submitProof) public {
_testBlock(name, _submitProof, 0);
}
Expand Down Expand Up @@ -998,4 +1005,17 @@ contract RollupTest is DecoderBase {

_rollup.submitEpochRootProof(_epochSize, args, fees, aggregationObject, proof);
}

function _quoteToSignedQuote(EpochProofQuoteLib.EpochProofQuote memory _quote)
internal
view
returns (EpochProofQuoteLib.SignedEpochProofQuote memory)
{
bytes32 digest = rollup.quoteToDigest(_quote);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return EpochProofQuoteLib.SignedEpochProofQuote({
quote: _quote,
signature: SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s})
});
}
}
12 changes: 8 additions & 4 deletions yarn-project/sequencer-client/src/publisher/l1-publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,8 +239,11 @@ export class L1Publisher {
// FIXME: This should not throw if unable to propose but return a falsey value, so
// we can differentiate between errors when hitting the L1 rollup contract (eg RPC error)
// which may require a retry, vs actually not being the turn for proposing.
const ts = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
const [slot, blockNumber] = await this.rollupContract.read.canProposeAtTime([ts, `0x${archive.toString('hex')}`]);
const timeOfNextL1Slot = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
const [slot, blockNumber] = await this.rollupContract.read.canProposeAtTime([
timeOfNextL1Slot,
`0x${archive.toString('hex')}`,
]);
return [slot, blockNumber];
}

Expand Down Expand Up @@ -302,9 +305,10 @@ export class L1Publisher {
}

public async validateProofQuote(quote: EpochProofQuote): Promise<EpochProofQuote | undefined> {
const args = [quote.toViemArgs()] as const;
const timeOfNextL1Slot = BigInt((await this.publicClient.getBlock()).timestamp + BigInt(ETHEREUM_SLOT_DURATION));
const args = [timeOfNextL1Slot, quote.toViemArgs()] as const;
try {
await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account });
await this.rollupContract.read.validateEpochProofRightClaimAtTime(args, { account: this.account });
} catch (err) {
const errorName = tryGetCustomErrorName(err);
this.log.warn(`Proof quote validation failed: ${errorName}`);
Expand Down

0 comments on commit 284c8f8

Please sign in to comment.