Skip to content

Commit 273b452

Browse files
authored
feat: moving fee payout + make proof submission sequential (#8262)
Fixing #7622 and #8259
1 parent 4a82f53 commit 273b452

File tree

7 files changed

+102
-87
lines changed

7 files changed

+102
-87
lines changed

l1-contracts/src/core/Rollup.sol

+29-65
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
3939
bytes32 archive;
4040
bytes32 blockHash;
4141
uint128 slotNumber;
42-
bool isProven;
4342
}
4443

4544
// @note The number of slots within which a block must be proven
@@ -97,8 +96,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
9796
blocks[0] = BlockLog({
9897
archive: bytes32(Constants.GENESIS_ARCHIVE_ROOT),
9998
blockHash: bytes32(0),
100-
slotNumber: 0,
101-
isProven: true
99+
slotNumber: 0
102100
});
103101
pendingBlockCount = 1;
104102
provenBlockCount = 1;
@@ -151,11 +149,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
151149
onlyOwner
152150
{
153151
if (blockNumber > provenBlockCount && blockNumber <= pendingBlockCount) {
154-
for (uint256 i = provenBlockCount; i < blockNumber; i++) {
155-
blocks[i].isProven = true;
156-
emit L2ProofVerified(i, "CHEAT");
157-
}
158-
_progressState();
152+
provenBlockCount = blockNumber;
159153
}
160154
assumeProvenUntilBlockNumber = blockNumber;
161155
}
@@ -274,6 +268,12 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
274268
revert Errors.Rollup__TryingToProveNonExistingBlock();
275269
}
276270

271+
// @note This implicitly also ensures that we have not already proven, since
272+
// the value `provenBlockCount` is incremented at the end of this function
273+
if (header.globalVariables.blockNumber != provenBlockCount) {
274+
revert Errors.Rollup__NonSequentialProving();
275+
}
276+
277277
bytes32 expectedLastArchive = blocks[header.globalVariables.blockNumber - 1].archive;
278278
// We do it this way to provide better error messages than passing along the storage values
279279
// TODO(#4148) Proper genesis state. If the state is empty, we allow anything for now.
@@ -356,24 +356,22 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
356356
revert Errors.Rollup__InvalidProof();
357357
}
358358

359-
blocks[header.globalVariables.blockNumber].isProven = true;
359+
provenBlockCount += 1;
360360

361-
_progressState();
361+
for (uint256 i = 0; i < 32; i++) {
362+
address coinbase = address(uint160(uint256(publicInputs[25 + i * 2])));
363+
uint256 fees = uint256(publicInputs[26 + i * 2]);
362364

365+
if (coinbase != address(0) && fees > 0) {
366+
// @note This will currently fail if there are insufficient funds in the bridge
367+
// which WILL happen for the old version after an upgrade where the bridge follow.
368+
// Consider allowing a failure. See #7938.
369+
FEE_JUICE_PORTAL.distributeFees(coinbase, fees);
370+
}
371+
}
363372
emit L2ProofVerified(header.globalVariables.blockNumber, _proverId);
364373
}
365374

366-
/**
367-
* @notice Get the `isProven` flag for the block number
368-
*
369-
* @param _blockNumber - The block number to check
370-
*
371-
* @return bool - True if proven, false otherwise
372-
*/
373-
function isBlockProven(uint256 _blockNumber) external view override(IRollup) returns (bool) {
374-
return blocks[_blockNumber].isProven;
375-
}
376-
377375
/**
378376
* @notice Get the archive root of a specific block
379377
*
@@ -476,8 +474,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
476474
blocks[pendingBlockCount++] = BlockLog({
477475
archive: _archive,
478476
blockHash: _blockHash,
479-
slotNumber: header.globalVariables.slotNumber.toUint128(),
480-
isProven: false
477+
slotNumber: header.globalVariables.slotNumber.toUint128()
481478
});
482479

483480
// @note The block number here will always be >=1 as the genesis block is at 0
@@ -494,22 +491,20 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
494491
header.globalVariables.blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight
495492
);
496493

497-
// @note This should be addressed at the time of proving if sequential proving or at the time of
498-
// inclusion into the proven chain otherwise. See #7622.
499-
if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) {
500-
// @note This will currently fail if there are insufficient funds in the bridge
501-
// which WILL happen for the old version after an upgrade where the bridge follow.
502-
// Consider allowing a failure. See #7938.
503-
FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees);
504-
}
505-
506494
emit L2BlockProcessed(header.globalVariables.blockNumber);
507495

508496
// Automatically flag the block as proven if we have cheated and set assumeProvenUntilBlockNumber.
509497
if (header.globalVariables.blockNumber < assumeProvenUntilBlockNumber) {
510-
blocks[header.globalVariables.blockNumber].isProven = true;
498+
provenBlockCount += 1;
499+
500+
if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) {
501+
// @note This will currently fail if there are insufficient funds in the bridge
502+
// which WILL happen for the old version after an upgrade where the bridge follow.
503+
// Consider allowing a failure. See #7938.
504+
FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees);
505+
}
506+
511507
emit L2ProofVerified(header.globalVariables.blockNumber, "CHEAT");
512-
_progressState();
513508
}
514509
}
515510

@@ -537,37 +532,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
537532
return blocks[pendingBlockCount - 1].archive;
538533
}
539534

540-
/**
541-
* @notice Progresses the state of the proven chain as far as possible
542-
*
543-
* @dev Emits `ProgressedState` if the state is progressed
544-
*
545-
* @dev Will continue along the pending chain as long as the blocks are proven
546-
* stops at the first unproven block.
547-
*
548-
* @dev Have a potentially unbounded gas usage. @todo Will need a bounded version, such that it cannot be
549-
* used as a DOS vector.
550-
*/
551-
function _progressState() internal {
552-
if (pendingBlockCount == provenBlockCount) {
553-
// We are already up to date
554-
return;
555-
}
556-
557-
uint256 cachedProvenBlockCount = provenBlockCount;
558-
559-
for (; cachedProvenBlockCount < pendingBlockCount; cachedProvenBlockCount++) {
560-
if (!blocks[cachedProvenBlockCount].isProven) {
561-
break;
562-
}
563-
}
564-
565-
if (cachedProvenBlockCount > provenBlockCount) {
566-
provenBlockCount = cachedProvenBlockCount;
567-
emit ProgressedState(provenBlockCount, pendingBlockCount);
568-
}
569-
}
570-
571535
/**
572536
* @notice Validates the header for submission
573537
*

l1-contracts/src/core/interfaces/IRollup.sol

-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ interface ITestRollup {
1818
interface IRollup {
1919
event L2BlockProcessed(uint256 indexed blockNumber);
2020
event L2ProofVerified(uint256 indexed blockNumber, bytes32 indexed proverId);
21-
event ProgressedState(uint256 provenBlockCount, uint256 pendingBlockCount);
2221
event PrunedPending(uint256 provenBlockCount, uint256 pendingBlockCount);
2322

2423
function canProposeAtTime(uint256 _ts, address _proposer, bytes32 _archive)
@@ -81,6 +80,5 @@ interface IRollup {
8180
// ) external;
8281

8382
function archive() external view returns (bytes32);
84-
function isBlockProven(uint256 _blockNumber) external view returns (bool);
8583
function archiveAt(uint256 _blockNumber) external view returns (bytes32);
8684
}

l1-contracts/src/core/libraries/Errors.sol

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ library Errors {
6161
error Rollup__UnavailableTxs(bytes32 txsHash); // 0x414906c3
6262
error Rollup__NothingToPrune(); // 0x850defd3
6363
error Rollup__NotReadyToPrune(uint256 currentSlot, uint256 prunableAt); // 0x9fdf1614
64+
error Rollup__NonSequentialProving(); // 0x1e5be132
6465

6566
// Registry
6667
error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf

l1-contracts/test/Rollup.t.sol

+51-17
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,27 @@ contract RollupTest is DecoderBase {
7878
_;
7979
}
8080

81+
function testRevertProveTwice() public setUpFor("mixed_block_1") {
82+
DecoderBase.Data memory data = load("mixed_block_1").block;
83+
bytes memory header = data.header;
84+
bytes32 archive = data.archive;
85+
bytes memory body = data.body;
86+
87+
// Progress time as necessary
88+
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
89+
availabilityOracle.publish(body);
90+
91+
// We jump to the time of the block. (unless it is in the past)
92+
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
93+
94+
rollup.process(header, archive, bytes32(0));
95+
96+
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
97+
98+
vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonSequentialProving.selector));
99+
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
100+
}
101+
81102
function testTimestamp() public setUpFor("mixed_block_1") {
82103
// Ensure that the timestamp of the current slot is never in the future.
83104
for (uint256 i = 0; i < 100; i++) {
@@ -105,7 +126,7 @@ contract RollupTest is DecoderBase {
105126
_testBlock("mixed_block_1", false);
106127

107128
uint256 currentSlot = rollup.getCurrentSlot();
108-
(,, uint128 slot,) = rollup.blocks(1);
129+
(,, uint128 slot) = rollup.blocks(1);
109130
uint256 prunableAt = uint256(slot) + rollup.TIMELINESS_PROVING_IN_SLOTS();
110131

111132
vm.expectRevert(
@@ -127,7 +148,7 @@ contract RollupTest is DecoderBase {
127148
// Even if we end up reverting block 1, we should still see the same root in the inbox.
128149
bytes32 inboxRoot2 = inbox.getRoot(2);
129150

130-
(,, uint128 slot,) = rollup.blocks(1);
151+
(,, uint128 slot) = rollup.blocks(1);
131152
uint256 prunableAt = uint256(slot) + rollup.TIMELINESS_PROVING_IN_SLOTS();
132153

133154
uint256 timeOfPrune = rollup.getTimestampForSlot(prunableAt);
@@ -198,6 +219,14 @@ contract RollupTest is DecoderBase {
198219
// We jump to the time of the block. (unless it is in the past)
199220
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
200221

222+
address coinbase = data.decodedHeader.globalVariables.coinbase;
223+
uint256 coinbaseBalance = portalERC20.balanceOf(coinbase);
224+
assertEq(coinbaseBalance, 0, "invalid initial coinbase balance");
225+
226+
// Assert that balance have NOT been increased by proposing the block
227+
rollup.process(header, archive, bytes32(0));
228+
assertEq(portalERC20.balanceOf(coinbase), 0, "invalid coinbase balance");
229+
201230
vm.expectRevert(
202231
abi.encodeWithSelector(
203232
IERC20Errors.ERC20InsufficientBalance.selector,
@@ -206,15 +235,13 @@ contract RollupTest is DecoderBase {
206235
feeAmount
207236
)
208237
);
209-
rollup.process(header, archive, bytes32(0));
210-
211-
address coinbase = data.decodedHeader.globalVariables.coinbase;
212-
uint256 coinbaseBalance = portalERC20.balanceOf(coinbase);
213-
assertEq(coinbaseBalance, 0, "invalid initial coinbase balance");
238+
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
239+
assertEq(portalERC20.balanceOf(coinbase), 0, "invalid coinbase balance");
214240

215241
portalERC20.mint(address(feeJuicePortal), feeAmount - portalBalance);
216242

217-
rollup.process(header, archive, bytes32(0));
243+
// When the block is proven we should have received the funds
244+
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
218245
assertEq(portalERC20.balanceOf(coinbase), feeAmount, "invalid coinbase balance");
219246
}
220247

@@ -237,9 +264,18 @@ contract RollupTest is DecoderBase {
237264

238265
function testConsecutiveMixedBlocksNonSequentialProof() public setUpFor("mixed_block_1") {
239266
_testBlock("mixed_block_1", false);
240-
_testBlock("mixed_block_2", true);
241267

242-
assertTrue(rollup.isBlockProven(2), "Block 2 is not proven");
268+
DecoderBase.Data memory data = load("mixed_block_2").block;
269+
bytes memory header = data.header;
270+
bytes32 archive = data.archive;
271+
bytes memory body = data.body;
272+
273+
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
274+
availabilityOracle.publish(body);
275+
rollup.process(header, archive, bytes32(0));
276+
277+
vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonSequentialProving.selector));
278+
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
243279

244280
assertEq(rollup.pendingBlockCount(), 3, "Invalid pending block count");
245281
assertEq(rollup.provenBlockCount(), 1, "Invalid proven block count");
@@ -366,9 +402,8 @@ contract RollupTest is DecoderBase {
366402

367403
function testSubmitProofInvalidArchive() public setUpFor("empty_block_1") {
368404
_testBlock("empty_block_1", false);
369-
_testBlock("empty_block_2", false);
370405

371-
DecoderBase.Data memory data = load("empty_block_2").block;
406+
DecoderBase.Data memory data = load("empty_block_1").block;
372407
bytes memory header = data.header;
373408
bytes32 archive = data.archive;
374409

@@ -379,7 +414,7 @@ contract RollupTest is DecoderBase {
379414

380415
vm.expectRevert(
381416
abi.encodeWithSelector(
382-
Errors.Rollup__InvalidArchive.selector, rollup.archiveAt(1), 0xdeadbeef
417+
Errors.Rollup__InvalidArchive.selector, rollup.archiveAt(0), 0xdeadbeef
383418
)
384419
);
385420
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
@@ -433,12 +468,11 @@ contract RollupTest is DecoderBase {
433468
rollup.process(header, archive, bytes32(0));
434469

435470
if (_submitProof) {
471+
uint256 pre = rollup.provenBlockCount();
472+
436473
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
437474

438-
assertTrue(
439-
rollup.isBlockProven(full.block.decodedHeader.globalVariables.blockNumber),
440-
"Block not proven"
441-
);
475+
assertEq(pre + 1, rollup.provenBlockCount(), "Block not proven");
442476
}
443477

444478
bytes32 l2ToL1MessageTreeRoot;

yarn-project/end-to-end/src/e2e_fees/failures.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
PublicFeePaymentMethod,
99
TxStatus,
1010
computeSecretHash,
11+
sleep,
1112
} from '@aztec/aztec.js';
1213
import { Gas, GasSettings } from '@aztec/circuits.js';
1314
import { FunctionType } from '@aztec/foundation/abi';
@@ -94,9 +95,16 @@ describe('e2e_fees failures', () => {
9495
.wait({ dontThrowOnRevert: true });
9596

9697
expect(txReceipt.status).toBe(TxStatus.APP_LOGIC_REVERTED);
98+
99+
// We wait until the block is proven since that is when the payout happens.
100+
const bn = await t.aztecNode.getBlockNumber();
101+
while ((await t.aztecNode.getProvenBlockNumber()) < bn) {
102+
await sleep(1000);
103+
}
104+
97105
const feeAmount = txReceipt.transactionFee!;
98-
const newSequencerL1Gas = await t.getCoinbaseBalance();
99-
expect(newSequencerL1Gas).toEqual(currentSequencerL1Gas + feeAmount);
106+
const newSequencerL1FeeAssetBalance = await t.getCoinbaseBalance();
107+
expect(newSequencerL1FeeAssetBalance).toEqual(currentSequencerL1Gas + feeAmount);
100108

101109
// and thus we paid the fee
102110
await expectMapping(

yarn-project/end-to-end/src/e2e_fees/fees_test.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
TokenContract,
2828
} from '@aztec/noir-contracts.js';
2929
import { getCanonicalFeeJuice } from '@aztec/protocol-contracts/fee-juice';
30+
import { type ProverNode } from '@aztec/prover-node';
3031

3132
import { getContract } from 'viem';
3233

@@ -53,6 +54,7 @@ export class FeesTest {
5354
public logger: DebugLogger;
5455
public pxe!: PXE;
5556
public aztecNode!: AztecNode;
57+
public proverNode!: ProverNode;
5658

5759
public aliceWallet!: AccountWallet;
5860
public aliceAddress!: AztecAddress;
@@ -167,9 +169,10 @@ export class FeesTest {
167169
await this.snapshotManager.snapshot(
168170
'initial_accounts',
169171
addAccounts(3, this.logger),
170-
async ({ accountKeys }, { pxe, aztecNode, aztecNodeConfig }) => {
172+
async ({ accountKeys }, { pxe, aztecNode, aztecNodeConfig, proverNode }) => {
171173
this.pxe = pxe;
172174
this.aztecNode = aztecNode;
175+
this.proverNode = proverNode;
173176
const accountManagers = accountKeys.map(ak => getSchnorrAccount(pxe, ak[0], ak[1], 1));
174177
await Promise.all(accountManagers.map(a => a.register()));
175178
this.wallets = await Promise.all(accountManagers.map(a => a.getWallet()));

yarn-project/end-to-end/src/e2e_fees/private_payments.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
PrivateFeePaymentMethod,
77
type TxReceipt,
88
computeSecretHash,
9+
sleep,
910
} from '@aztec/aztec.js';
1011
import { type GasSettings } from '@aztec/circuits.js';
1112
import { type TokenContract as BananaCoin, FPCContract } from '@aztec/noir-contracts.js';
@@ -138,6 +139,12 @@ describe('e2e_fees private_payment', () => {
138139
* TODO(6583): update this comment properly now that public execution consumes gas
139140
*/
140141

142+
// We wait until the block is proven since that is when the payout happens.
143+
const bn = await t.aztecNode.getBlockNumber();
144+
while ((await t.aztecNode.getProvenBlockNumber()) < bn) {
145+
await sleep(1000);
146+
}
147+
141148
// expect(tx.transactionFee).toEqual(200032492n);
142149
await expect(t.getCoinbaseBalance()).resolves.toEqual(InitialSequencerL1Gas + tx.transactionFee!);
143150
const [feeAmount, refundAmount] = getFeeAndRefund(tx);

0 commit comments

Comments
 (0)