Skip to content

Commit ac71126

Browse files
authored
fix: address potential deadlock (#16637)
Addresses a potential deadlock by introducing a `catchUp` function in the `inbox`
2 parents 8bd8f20 + ca8c97c commit ac71126

File tree

6 files changed

+234
-15
lines changed

6 files changed

+234
-15
lines changed

l1-contracts/src/core/RollupCore.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {RewardLib, RewardConfig} from "@aztec/core/libraries/rollup/RewardLib.so
3737
import {StakingQueueConfig} from "@aztec/core/libraries/compressed-data/StakingQueueConfig.sol";
3838
import {FeeConfigLib, CompressedFeeConfig} from "@aztec/core/libraries/compressed-data/fees/FeeConfig.sol";
3939
import {G1Point, G2Point} from "@aztec/shared/libraries/BN254Lib.sol";
40+
import {ChainTipsLib, CompressedChainTips} from "@aztec/core/libraries/compressed-data/Tips.sol";
4041

4142
/**
4243
* @title RollupCore
@@ -177,6 +178,7 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
177178
using TimeLib for Slot;
178179
using TimeLib for Epoch;
179180
using FeeConfigLib for CompressedFeeConfig;
181+
using ChainTipsLib for CompressedChainTips;
180182

181183
/**
182184
* @notice The L1 block number when this rollup was deployed
@@ -319,6 +321,13 @@ contract RollupCore is EIP712("Aztec Rollup", "1"), Ownable, IStakingCore, IVali
319321
uint256 currentManaTarget = FeeLib.getStorage().config.getManaTarget();
320322
require(_manaTarget >= currentManaTarget, Errors.Rollup__InvalidManaTarget(currentManaTarget, _manaTarget));
321323
FeeLib.updateManaTarget(_manaTarget);
324+
325+
// If we are going from 0 to non-zero mana limits, we need to catch up the inbox
326+
if (currentManaTarget == 0 && _manaTarget > 0) {
327+
RollupStore storage rollupStore = STFLib.getStorage();
328+
rollupStore.config.inbox.catchUp(rollupStore.tips.getPendingBlockNumber());
329+
}
330+
322331
emit IRollupCore.ManaTargetUpdated(_manaTarget);
323332
}
324333

l1-contracts/src/core/interfaces/messagebridge/IInbox.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ interface IInbox {
3030
*/
3131
event MessageSent(uint256 indexed l2BlockNumber, uint256 index, bytes32 indexed hash, bytes16 rollingHash);
3232

33+
event InboxSynchronized(uint256 indexed inProgress);
34+
3335
// docs:start:send_l1_to_l2_message
3436
/**
3537
* @notice Inserts a new message into the Inbox
@@ -59,6 +61,8 @@ interface IInbox {
5961
function consume(uint256 _toConsume) external returns (bytes32);
6062
// docs:end:consume
6163

64+
function catchUp(uint256 _pendingBlockNumber) external;
65+
6266
function getFeeAssetPortal() external view returns (address);
6367

6468
function getRoot(uint256 _blockNumber) external view returns (bytes32);

l1-contracts/src/core/messagebridge/Inbox.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {DataStructures} from "@aztec/core/libraries/DataStructures.sol";
1111
import {Errors} from "@aztec/core/libraries/Errors.sol";
1212
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
1313
import {IERC20} from "@oz/token/ERC20/IERC20.sol";
14+
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
1415

1516
/**
1617
* @title Inbox
@@ -153,6 +154,21 @@ contract Inbox is IInbox {
153154
return root;
154155
}
155156

157+
/**
158+
* @notice Catch up the inbox to the pending block number
159+
*
160+
* @dev Only callable by the rollup contract
161+
* Will only be called WHEN a change is made from 0 to non-zero mana limits
162+
*
163+
* @param _pendingBlockNumber - The pending block number to catch up to
164+
*/
165+
function catchUp(uint256 _pendingBlockNumber) external override(IInbox) {
166+
require(msg.sender == ROLLUP, Errors.Inbox__Unauthorized());
167+
// The next expected will be 1 ahead of the next block, e.g., + 2 from current.
168+
state.inProgress = SafeCast.toUint64(_pendingBlockNumber + 2);
169+
emit InboxSynchronized(state.inProgress);
170+
}
171+
156172
function getFeeAssetPortal() external view override(IInbox) returns (address) {
157173
return FEE_ASSET_PORTAL;
158174
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,22 @@
22
// Copyright 2024 Aztec Labs.
33
pragma solidity >=0.8.27;
44

5-
import {DecoderBase} from "./base/DecoderBase.sol";
5+
import {DecoderBase} from "../base/DecoderBase.sol";
66

77
import {Registry} from "@aztec/governance/Registry.sol";
88
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
99
import {TestERC20} from "@aztec/mock/TestERC20.sol";
10-
import {TestConstants} from "./harnesses/TestConstants.sol";
10+
import {TestConstants} from "../harnesses/TestConstants.sol";
1111
import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol";
1212
import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";
1313

1414
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
1515

1616
import {Errors} from "@aztec/core/libraries/Errors.sol";
1717

18-
import {RollupBase, IInstance} from "./base/RollupBase.sol";
19-
import {RollupBuilder} from "./builder/RollupBuilder.sol";
20-
import {TimeCheater} from "./staking/TimeCheater.sol";
18+
import {RollupBase, IInstance} from "../base/RollupBase.sol";
19+
import {RollupBuilder} from "../builder/RollupBuilder.sol";
20+
import {TimeCheater} from "../staking/TimeCheater.sol";
2121
import {Bps, BpsLib} from "@aztec/core/libraries/rollup/RewardLib.sol";
2222
// solhint-disable comprehensive-interface
2323

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2024 Aztec Labs.
3+
pragma solidity >=0.8.27;
4+
5+
import {DecoderBase} from "../base/DecoderBase.sol";
6+
7+
import {Registry} from "@aztec/governance/Registry.sol";
8+
import {FeeJuicePortal} from "@aztec/core/messagebridge/FeeJuicePortal.sol";
9+
import {TestERC20} from "@aztec/mock/TestERC20.sol";
10+
import {TestConstants} from "../harnesses/TestConstants.sol";
11+
import {RewardDistributor} from "@aztec/governance/RewardDistributor.sol";
12+
import {ProposeArgs, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";
13+
14+
import {Timestamp, Slot, Epoch, TimeLib} from "@aztec/core/libraries/TimeLib.sol";
15+
16+
import {Errors} from "@aztec/core/libraries/Errors.sol";
17+
import {ProposeArgs, ProposePayload, OracleInput, ProposeLib} from "@aztec/core/libraries/rollup/ProposeLib.sol";
18+
19+
import {RollupBase, IInstance} from "../base/RollupBase.sol";
20+
import {RollupBuilder} from "../builder/RollupBuilder.sol";
21+
import {TimeCheater} from "../staking/TimeCheater.sol";
22+
import {Bps, BpsLib} from "@aztec/core/libraries/rollup/RewardLib.sol";
23+
import {
24+
AttestationLib,
25+
Signature,
26+
CommitteeAttestation,
27+
CommitteeAttestations
28+
} from "@aztec/core/libraries/rollup/AttestationLib.sol";
29+
import {Constants} from "@aztec/core/libraries/ConstantsGen.sol";
30+
import {ProposedHeader} from "@aztec/core/libraries/rollup/ProposedHeaderLib.sol";
31+
import {SafeCast} from "@oz/utils/math/SafeCast.sol";
32+
import {AttestationLibHelper} from "@test/helper_libraries/AttestationLibHelper.sol";
33+
import {Ownable} from "@oz/access/Ownable.sol";
34+
import {IInbox} from "@aztec/core/interfaces/messagebridge/IInbox.sol";
35+
// solhint-disable comprehensive-interface
36+
37+
struct Block {
38+
ProposeArgs proposeArgs;
39+
bytes blobInputs;
40+
CommitteeAttestation[] attestations;
41+
address[] signers;
42+
}
43+
44+
/**
45+
* Blocks are generated using the `integration_l1_publisher.test.ts` tests.
46+
* Main use of these test is shorter cycles when updating the decoder contract.
47+
*/
48+
contract Tmnt223Test is RollupBase {
49+
using ProposeLib for ProposeArgs;
50+
using TimeLib for Timestamp;
51+
using TimeLib for Slot;
52+
using TimeLib for Epoch;
53+
54+
Registry internal registry;
55+
TestERC20 internal testERC20;
56+
FeeJuicePortal internal feeJuicePortal;
57+
RewardDistributor internal rewardDistributor;
58+
TimeCheater internal timeCheater;
59+
60+
uint256 internal SLOT_DURATION;
61+
uint256 internal EPOCH_DURATION;
62+
uint256 internal PROOF_SUBMISSION_EPOCHS;
63+
uint256 internal MANA_TARGET = 0;
64+
65+
address internal sequencer = address(bytes20("sequencer"));
66+
67+
DecoderBase.Full internal full;
68+
69+
/**
70+
* @notice Set up the contracts needed for the tests with time aligned to the provided block name
71+
*/
72+
modifier setUpFor(string memory _name) {
73+
{
74+
full = load(_name);
75+
Slot slotNumber = full.block.header.slotNumber;
76+
uint256 initialTime = Timestamp.unwrap(full.block.header.timestamp) - Slot.unwrap(slotNumber) * SLOT_DURATION;
77+
vm.warp(initialTime);
78+
}
79+
80+
TimeLib.initialize(
81+
block.timestamp,
82+
TestConstants.AZTEC_SLOT_DURATION,
83+
TestConstants.AZTEC_EPOCH_DURATION,
84+
TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS
85+
);
86+
SLOT_DURATION = TestConstants.AZTEC_SLOT_DURATION;
87+
EPOCH_DURATION = TestConstants.AZTEC_EPOCH_DURATION;
88+
PROOF_SUBMISSION_EPOCHS = TestConstants.AZTEC_PROOF_SUBMISSION_EPOCHS;
89+
timeCheater =
90+
new TimeCheater(address(this), block.timestamp, SLOT_DURATION, EPOCH_DURATION, PROOF_SUBMISSION_EPOCHS);
91+
92+
RollupBuilder builder = new RollupBuilder(address(this)).setManaTarget(MANA_TARGET).setTargetCommitteeSize(0);
93+
builder.deploy();
94+
95+
rollup = IInstance(address(builder.getConfig().rollup));
96+
testERC20 = builder.getConfig().testERC20;
97+
registry = builder.getConfig().registry;
98+
99+
feeJuicePortal = FeeJuicePortal(address(rollup.getFeeAssetPortal()));
100+
rewardDistributor = RewardDistributor(address(registry.getRewardDistributor()));
101+
102+
_;
103+
}
104+
105+
function test_deadlock() public setUpFor("empty_block_1") {
106+
skipBlobCheck(address(rollup));
107+
timeCheater.cheat__progressSlot();
108+
109+
for (uint256 i = 0; i < 10; i++) {
110+
Block memory l2Block = getBlock();
111+
rollup.propose(
112+
l2Block.proposeArgs,
113+
AttestationLibHelper.packAttestations(l2Block.attestations),
114+
l2Block.signers,
115+
l2Block.blobInputs
116+
);
117+
timeCheater.cheat__progressSlot();
118+
}
119+
120+
// Now say that we alter the mana limit! Ensure that we can still produce blocks!
121+
MANA_TARGET = 1e6;
122+
vm.expectEmit(true, true, true, true, address(rollup.getInbox()));
123+
emit IInbox.InboxSynchronized(12);
124+
vm.prank(Ownable(address(rollup)).owner());
125+
rollup.updateManaTarget(MANA_TARGET);
126+
127+
Block memory nonEmptyBlock = getBlock();
128+
rollup.propose(
129+
nonEmptyBlock.proposeArgs,
130+
AttestationLibHelper.packAttestations(nonEmptyBlock.attestations),
131+
nonEmptyBlock.signers,
132+
nonEmptyBlock.blobInputs
133+
);
134+
135+
assertEq(rollup.getPendingBlockNumber(), 11);
136+
assertEq(rollup.getInbox().getInProgress(), 13);
137+
}
138+
139+
function getBlock() internal view returns (Block memory) {
140+
// We will be using the genesis for both before and after. This will be impossible
141+
// to prove, but we don't need to prove anything here.
142+
bytes32 archiveRoot = bytes32(Constants.GENESIS_ARCHIVE_ROOT);
143+
144+
ProposedHeader memory header = full.block.header;
145+
146+
Slot slotNumber = rollup.getCurrentSlot();
147+
Timestamp ts = rollup.getTimestampForSlot(slotNumber);
148+
149+
// Updating the header with important information!
150+
header.lastArchiveRoot = archiveRoot;
151+
header.slotNumber = slotNumber;
152+
header.timestamp = ts;
153+
header.coinbase = address(bytes20("coinbase"));
154+
header.feeRecipient = bytes32(0);
155+
header.gasFees.feePerL2Gas = SafeCast.toUint128(rollup.getManaBaseFeeAt(Timestamp.wrap(block.timestamp), true));
156+
if (MANA_TARGET > 0) {
157+
header.totalManaUsed = MANA_TARGET;
158+
} else {
159+
header.totalManaUsed = 0;
160+
}
161+
162+
ProposeArgs memory proposeArgs = ProposeArgs({
163+
header: header,
164+
archive: archiveRoot,
165+
stateReference: EMPTY_STATE_REFERENCE,
166+
oracleInput: OracleInput({feeAssetPriceModifier: 0})
167+
});
168+
169+
CommitteeAttestation[] memory attestations = new CommitteeAttestation[](0);
170+
address[] memory signers = new address[](0);
171+
172+
return Block({
173+
proposeArgs: proposeArgs,
174+
blobInputs: full.block.blobCommitments,
175+
attestations: attestations,
176+
signers: signers
177+
});
178+
}
179+
}

yarn-project/end-to-end/src/fixtures/l1_to_l2_messaging.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,31 @@ export async function sendL1ToL2Message(
4848
throw new Error(`Receipt transaction hash mismatch: ${txReceipt.transactionHash} !== ${txHash}`);
4949
}
5050

51-
// Exactly 1 event should be emitted in the transaction
52-
if (txReceipt.logs.length !== 1) {
51+
// Filter for MessageSent events from the Inbox contract by trying to decode each log
52+
const messageSentLogs = txReceipt.logs
53+
.filter(log => log.address.toLowerCase() === ctx.l1ContractAddresses.inboxAddress.toString().toLowerCase())
54+
.map(log => {
55+
try {
56+
const decoded = decodeEventLog({
57+
abi: InboxAbi,
58+
data: log.data,
59+
topics: log.topics,
60+
});
61+
return { log, decoded };
62+
} catch {
63+
return null; // Not a decodable event from this ABI
64+
}
65+
})
66+
.filter((item): item is { log: any; decoded: any } => item !== null && item.decoded.eventName === 'MessageSent');
67+
68+
if (messageSentLogs.length !== 1) {
5369
throw new Error(
54-
`Wrong number of logs found in ${txHash} transaction (got ${txReceipt.logs.length} expected 1)\n${tryJsonStringify(txReceipt.logs)}`,
70+
`Wrong number of MessageSent logs found in ${txHash} transaction (got ${messageSentLogs.length} expected 1)\n${tryJsonStringify(messageSentLogs.map(item => item.log))}`,
5571
);
5672
}
5773

58-
// We decode the event and get leaf out of it
59-
const messageSentLog = txReceipt.logs[0];
60-
const topics = decodeEventLog({
61-
abi: InboxAbi,
62-
data: messageSentLog.data,
63-
topics: messageSentLog.topics,
64-
});
74+
// We already have the decoded event
75+
const topics = messageSentLogs[0].decoded;
6576
const receivedMsgHash = topics.args.hash;
6677
const receivedGlobalLeafIndex = topics.args.index;
6778

0 commit comments

Comments
 (0)