Skip to content
Open
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
72 changes: 36 additions & 36 deletions l1-contracts/partial_epoch_proof_gas_report.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,66 +8,66 @@
"functions": {
"gasReportSubmit16Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 1301116,
"mean": 1301116,
"median": 1301116,
"max": 1301116
"min": 1301265,
"mean": 1301265,
"median": 1301265,
"max": 1301265
},
"gasReportSubmit16CheckpointsWithTwoOverrides((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 1466779,
"mean": 1466779,
"median": 1466779,
"max": 1466779
"min": 1466256,
"mean": 1466256,
"median": 1466256,
"max": 1466256
},
"gasReportSubmit1Checkpoint((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 664809,
"mean": 664809,
"median": 664809,
"max": 664809
"min": 664958,
"mean": 664958,
"median": 664958,
"max": 664958
},
"gasReportSubmit1CheckpointWithTwoOverrides((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 691596,
"mean": 691596,
"median": 691596,
"max": 691596
"min": 691705,
"mean": 691705,
"median": 691705,
"max": 691705
},
"gasReportSubmit32Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 1821204,
"mean": 1821204,
"median": 1821204,
"max": 1821204
"min": 1821353,
"mean": 1821353,
"median": 1821353,
"max": 1821353
},
"gasReportSubmit32CheckpointsWithTwoOverrides((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 2079915,
"mean": 2079915,
"median": 2079915,
"max": 2079915
"min": 2078996,
"mean": 2078996,
"median": 2078996,
"max": 2078996
},
"gasReportSubmit8Checkpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 986773,
"mean": 986773,
"median": 986773,
"max": 986773
"min": 986922,
"mean": 986922,
"median": 986922,
"max": 986922
},
"gasReportSubmit8CheckpointsWithTwoOverrides((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 1083754,
"mean": 1083754,
"median": 1083754,
"max": 1083754
"min": 1083547,
"mean": 1083547,
"median": 1083547,
"max": 1083547
},
"gasReportSubmit8MoreCheckpoints((uint256,uint256,(bytes32,bytes32,bytes32,bytes32,bytes32,address),(bytes32,bytes32,bytes32,bytes32,bytes32,uint256,uint256,address,bytes32,(uint128,uint128),uint256,uint256)[],(bytes,bytes),bytes,bytes))": {
"calls": 1,
"min": 994477,
"mean": 994477,
"median": 994477,
"max": 994477
"min": 958650,
"mean": 958650,
"median": 958650,
"max": 958650
}
}
}
Expand Down
18 changes: 9 additions & 9 deletions l1-contracts/partial_epoch_proof_gas_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

| Proof submission | Gas |
|---|---:|
| 1 Checkpoint | 664,809 |
| 1 Checkpoint With Two Overrides | 691,596 |
| 8 Checkpoints | 986,773 |
| 8 Checkpoints With Two Overrides | 1,083,754 |
| 8 More Checkpoints | 994,477 |
| 16 Checkpoints | 1,301,116 |
| 16 Checkpoints With Two Overrides | 1,466,779 |
| 32 Checkpoints | 1,821,204 |
| 32 Checkpoints With Two Overrides | 2,079,915 |
| 1 Checkpoint | 664,958 |
| 1 Checkpoint With Two Overrides | 691,705 |
| 8 Checkpoints | 986,922 |
| 8 Checkpoints With Two Overrides | 1,083,547 |
| 8 More Checkpoints | 958,650 |
| 16 Checkpoints | 1,301,265 |
| 16 Checkpoints With Two Overrides | 1,466,256 |
| 32 Checkpoints | 1,821,353 |
| 32 Checkpoints With Two Overrides | 2,078,996 |

_Uses the mock epoch proof verifier._
44 changes: 29 additions & 15 deletions l1-contracts/src/core/libraries/rollup/EpochProofLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,17 @@ library EpochProofLib {
STFLib.prune();
}

Epoch endEpoch = assertAcceptable(_args.start, _args.end);
(Epoch endEpoch, uint256 provenBeforeSubmission) = assertAcceptable(_args.start, _args.end);
uint256 firstHeaderToVerify;
if (provenBeforeSubmission >= _args.start) {
uint256 provenPrefixLength = provenBeforeSubmission - _args.start + 1;
uint256 accountedPrefixLength = RewardLib.getLongestProvenLength(endEpoch);
Comment on lines +120 to +121

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would these two differ?

firstHeaderToVerify = provenPrefixLength < accountedPrefixLength ? provenPrefixLength : accountedPrefixLength;
}

// Rehash the supplied headers against storage once, here: the public-input assembly below reads the fee
// recipient/value out of them and relies on this call having run.
verifyHeaders(_args.start, _args.end, _args.headers);
// The skipped calldata prefix is untrusted, but rewards have already consumed it and proof verification binds its
// fee data to the canonical checkpoint headers. We only verify new headers since the last proof
verifyHeaders(_args.start, _args.end, _args.headers, firstHeaderToVerify);

// Verify attestations for the last checkpoint in the epoch
// -> This serves as training wheels for the public part of the system (proving systems used in public and AVM)
Expand Down Expand Up @@ -162,8 +168,8 @@ library EpochProofLib {
*
* @dev The fee recipient/value public inputs are sourced from the supplied headers, so this entry point rehashes
* them against storage before assembling: an off-chain caller must not walk away with public inputs built from
* unverified fee fields and only discover the mismatch when the on-chain proof reverts. The submit path verifies
* the headers up front and assembles via computeEpochProofPublicInputs to avoid rehashing them twice.
* unverified fee fields and only discover the mismatch when the on-chain proof reverts. The submit path separately
* validates headers that have not already been proven and accounted for.
*
* @param _start - The start of the epoch (inclusive)
* @param _end - The end of the epoch (inclusive)
Expand All @@ -178,7 +184,7 @@ library EpochProofLib {
ProposedHeader[] calldata _headers,
bytes calldata _blobPublicInputs
) internal view returns (bytes32[] memory) {
verifyHeaders(_start, _end, _headers);
verifyHeaders(_start, _end, _headers, 0);
return computeEpochProofPublicInputs(_start, _end, _args, _headers, _blobPublicInputs);
}

Expand Down Expand Up @@ -396,19 +402,25 @@ library EpochProofLib {
}

/**
* @notice Rehashes each provided checkpoint header and requires it to match the stored header hash
* @notice Rehashes a suffix of the provided checkpoint headers and requires it to match the stored header hashes
*
* @param _start The first checkpoint number in the epoch (inclusive)
* @param _end The last checkpoint number in the epoch (inclusive)
* @param _headers The proposed headers for each checkpoint in [_start, _end]
* @param _firstHeaderToVerify The index of the first header that has not already been proven and accounted for
*/
function verifyHeaders(uint256 _start, uint256 _end, ProposedHeader[] calldata _headers) private view {
function verifyHeaders(
uint256 _start,
uint256 _end,
ProposedHeader[] calldata _headers,
uint256 _firstHeaderToVerify
) private view {
uint256 numCheckpoints = _end - _start + 1;
require(
_headers.length == numCheckpoints, Errors.Rollup__InvalidCheckpointHeaderCount(numCheckpoints, _headers.length)
);

for (uint256 i = 0; i < numCheckpoints; i++) {
for (uint256 i = _firstHeaderToVerify; i < numCheckpoints; i++) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the whole patch.

bytes32 expectedHeaderHash = STFLib.getHeaderHash(_start + i);
bytes32 providedHeaderHash = ProposedHeaderLib.hash(_headers[i]);
require(
Expand Down Expand Up @@ -439,8 +451,9 @@ library EpochProofLib {
* @param _start The first checkpoint number in the epoch (inclusive)
* @param _end The last checkpoint number in the epoch (inclusive)
* @return The epoch number that the proof covers
* @return The proven checkpoint number observed while checking the submission
*/
function assertAcceptable(uint256 _start, uint256 _end) private view returns (Epoch) {
function assertAcceptable(uint256 _start, uint256 _end) private view returns (Epoch, uint256) {
RollupStore storage rollupStore = STFLib.getStorage();

Epoch startEpoch = STFLib.getEpochForCheckpoint(_start);
Expand All @@ -465,7 +478,8 @@ library EpochProofLib {
bool isStartOfEpoch = _start == 1 || parentEpoch <= startEpoch - Epoch.wrap(1);
require(isStartOfEpoch, Errors.Rollup__StartIsNotFirstCheckpointOfEpoch());

bool isStartBuildingOnProven = _start - 1 <= rollupStore.tips.getProven();
uint256 provenBeforeSubmission = rollupStore.tips.getProven();
bool isStartBuildingOnProven = _start - 1 <= provenBeforeSubmission;
require(isStartBuildingOnProven, Errors.Rollup__StartIsNotBuildingOnProven());

bool claimedNumCheckpointsInEpoch = _end - _start + 1 <= Constants.MAX_CHECKPOINTS_PER_EPOCH;
Expand All @@ -474,7 +488,7 @@ library EpochProofLib {
Errors.Rollup__TooManyCheckpointsInEpoch(Constants.MAX_CHECKPOINTS_PER_EPOCH, _end - _start)
);

return endEpoch;
return (endEpoch, provenBeforeSubmission);
}

/**
Expand All @@ -485,8 +499,8 @@ library EpochProofLib {
* 2. Assembling the public inputs for the root rollup circuit
* 3. Verifying the validity proof against the assembled public inputs using the configured verifier
*
* @dev Assumes the caller has already verified the supplied checkpoint headers against storage, so assembly skips
* rehashing them.
* @dev Assumes the caller has completed the submit path's required header checks, so assembly does not rehash
* headers.
*
* @dev Errors Thrown:
* - Rollup__InvalidBlobProof: Batched blob proof verification failed
Expand Down
4 changes: 4 additions & 0 deletions l1-contracts/src/core/libraries/rollup/RewardLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,10 @@ library RewardLib {
return getStorage().epochRewards[_epoch].rewards;
}

function getLongestProvenLength(Epoch _epoch) internal view returns (uint256) {
return getStorage().epochRewards[_epoch].longestProvenLength;
}

function getHasSubmitted(Epoch _epoch, uint256 _length, address _prover) internal view returns (bool) {
return getStorage().epochRewards[_epoch].subEpoch[_length].shares[_prover] > 0;
}
Expand Down
73 changes: 73 additions & 0 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,79 @@ contract RollupTest is RollupBase {
assertEq(outbox.getRootData(Epoch.wrap(0), 2), outHash2, "Root at K=2 should be outHash2");
}

function testLongerEpochProofAllowsModifiedPreviouslyProvenHeader() public setUpFor("mixed_checkpoint_1") {
_proposeCheckpoint("mixed_checkpoint_1", 1);
_proposeCheckpoint("mixed_checkpoint_2", 2);

DecoderBase.Data memory checkpoint1Data = load("mixed_checkpoint_1").checkpoint;
DecoderBase.Data memory checkpoint2Data = load("mixed_checkpoint_2").checkpoint;
CheckpointLog memory checkpoint = rollup.getCheckpoint(0);

_submitEpochProof(
1,
1,
checkpoint.archive,
checkpoint1Data.archive,
checkpoint1Data.batchedBlobInputs,
checkpoint1Data.header.outHash
);

address modifiedCoinbase = makeAddr("modifiedCoinbase");

// even though his header was tampered with the next _submitEpochProof call will succeed (with a MockVerifier):
// the correct header for slot 1 was correct when the previous proof was sent
// the fact that it is now bogus does no matter because its rewards will not be processed again
// with a RealVerifier the proof will fail because the header's hash is sent as a public input
proposedHeaders[1].coinbase = modifiedCoinbase;

_submitEpochProof(
1,
2,
checkpoint.archive,
checkpoint2Data.archive,
checkpoint2Data.batchedBlobInputs,
checkpoint2Data.header.outHash
);

assertEq(rollup.getProvenCheckpointNumber(), 2);
assertEq(rollup.getSequencerRewards(modifiedCoinbase), 0);
}

function testLongerEpochProofRejectsModifiedNewHeader() public setUpFor("mixed_checkpoint_1") {
_proposeCheckpoint("mixed_checkpoint_1", 1);
_proposeCheckpoint("mixed_checkpoint_2", 2);

DecoderBase.Data memory checkpoint1Data = load("mixed_checkpoint_1").checkpoint;
DecoderBase.Data memory checkpoint2Data = load("mixed_checkpoint_2").checkpoint;
CheckpointLog memory checkpoint = rollup.getCheckpoint(0);

_submitEpochProof(
1,
1,
checkpoint.archive,
checkpoint1Data.archive,
checkpoint1Data.batchedBlobInputs,
checkpoint1Data.header.outHash
);

bytes32 expectedHeaderHash = ProposedHeaderLib.hash(proposedHeaders[2]);
// send bogus header
proposedHeaders[2].accumulatedFees += 1;
bytes32 providedHeaderHash = ProposedHeaderLib.hash(proposedHeaders[2]);

vm.expectRevert(
abi.encodeWithSelector(Errors.Rollup__InvalidCheckpointHeader.selector, expectedHeaderHash, providedHeaderHash)
);
_submitEpochProof(
1,
2,
checkpoint.archive,
checkpoint2Data.archive,
checkpoint2Data.batchedBlobInputs,
checkpoint2Data.header.outHash
);
}

// getEpochProofPublicInputs is the view that the prover-publisher calls off-chain to validate its inputs before
// submitting. Because the fee recipient/value public inputs are taken from the supplied headers, the header check
// must run here too - not only on the submit path - so a mismatch is caught before publishing rather than reverting
Expand Down
Loading