Skip to content

Commit

Permalink
fix(protocol): fix block timestamp check (#18759)
Browse files Browse the repository at this point in the history
  • Loading branch information
dantaik authored Jan 13, 2025
1 parent 5d576b9 commit 5d28e03
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 51 deletions.
8 changes: 4 additions & 4 deletions packages/protocol/contracts/layer1/based/ITaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import "src/shared/based/LibSharedData.sol";
/// @custom:security-contact [email protected]
interface ITaikoInbox {
struct BlockParams {
// the max nubmer of transactions in this block. Note that if there are not enough
// the max number of transactions in this block. Note that if there are not enough
// transactions in calldata or blobs, the block will contains as many transactions as
// possible.
uint16 numTransactions;
Expand All @@ -35,7 +35,7 @@ interface ITaikoInbox {
bytes32 parentMetaHash;
uint64 anchorBlockId;
bytes32 anchorInput;
uint64 timestamp;
uint64 lastBlockTimestamp;
uint32 txListOffset;
uint32 txListSize;
// The index of the first blob in this batch.
Expand All @@ -55,7 +55,7 @@ interface ITaikoInbox {
address coinbase;
uint64 batchId;
uint32 gasLimit;
uint64 timestamp;
uint64 lastBlockTimestamp;
bytes32 parentMetaHash;
address proposer;
uint96 livenessBond;
Expand Down Expand Up @@ -85,7 +85,7 @@ interface ITaikoInbox {
uint64 lastBlockId;
uint192 _reserved3;
uint64 batchId; // slot 3
uint64 timestamp;
uint64 lastBlockTimestamp;
uint64 anchorBlockId;
uint24 nextTransitionId;
uint8 reserved4;
Expand Down
59 changes: 26 additions & 33 deletions packages/protocol/contracts/layer1/based/TaikoInbox.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,10 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
}

bool calldataUsed = _txList.length != 0;
UpdatedParams memory updatedParams;

require(calldataUsed || params.numBlobs != 0, BlobNotSpecified());

updatedParams = _validateBatchParams(
(uint64 anchorBlockId, uint64 lastBlockTimestamp) = _validateBatchParams(
params,
config.maxAnchorHeightOffset,
config.maxSignalsToReceive,
Expand All @@ -133,7 +132,7 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
coinbase: params.coinbase,
batchId: stats2.numBatches,
gasLimit: config.blockMaxGasLimit,
timestamp: updatedParams.timestamp,
lastBlockTimestamp: lastBlockTimestamp,
parentMetaHash: lastBatch.metaHash,
proposer: params.proposer,
livenessBond: config.livenessBond,
Expand All @@ -142,8 +141,8 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
txListOffset: params.txListOffset,
txListSize: params.txListSize,
numBlobs: calldataUsed ? 0 : params.numBlobs,
anchorBlockId: updatedParams.anchorBlockId,
anchorBlockHash: blockhash(updatedParams.anchorBlockId),
anchorBlockId: anchorBlockId,
anchorBlockHash: blockhash(anchorBlockId),
signalSlots: params.signalSlots,
blocks: params.blocks,
anchorInput: params.anchorInput,
Expand All @@ -159,8 +158,8 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {

// SSTORE #2 {{
batch.batchId = stats2.numBatches;
batch.timestamp = updatedParams.timestamp;
batch.anchorBlockId = updatedParams.anchorBlockId;
batch.lastBlockTimestamp = lastBlockTimestamp;
batch.anchorBlockId = anchorBlockId;
batch.nextTransitionId = 1;
batch.verifiedTransitionId = 0;
batch.reserved4 = 0;
Expand Down Expand Up @@ -437,7 +436,7 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {

Batch storage batch = state.batches[0];
batch.metaHash = bytes32(uint256(1));
batch.timestamp = uint64(block.timestamp);
batch.lastBlockTimestamp = uint64(block.timestamp);
batch.anchorBlockId = uint64(block.number);
batch.nextTransitionId = 2;
batch.verifiedTransitionId = 1;
Expand Down Expand Up @@ -605,11 +604,11 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
)
private
view
returns (UpdatedParams memory updatedParams_)
returns (uint64 anchorBlockId_, uint64 lastBlockTimestamp_)
{
unchecked {
if (_params.anchorBlockId == 0) {
updatedParams_.anchorBlockId = uint64(block.number - 1);
anchorBlockId_ = uint64(block.number - 1);
} else {
require(
_params.anchorBlockId + _maxAnchorHeightOffset >= block.number,
Expand All @@ -620,31 +619,30 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {
_params.anchorBlockId >= _lastBatch.anchorBlockId,
AnchorBlockIdSmallerThanParent()
);
updatedParams_.anchorBlockId = _params.anchorBlockId;
anchorBlockId_ = _params.anchorBlockId;
}

if (_params.timestamp == 0) {
updatedParams_.timestamp = uint64(block.timestamp);
} else {
// Verify the provided timestamp to anchor. Note that params_.anchorBlockId
// and params_.timestamp may not correspond to the same L1 block.
require(
_params.timestamp + _maxAnchorHeightOffset * LibNetwork.ETHEREUM_BLOCK_TIME
>= block.timestamp,
TimestampTooSmall()
);
require(_params.timestamp >= _lastBatch.timestamp, TimestampSmallerThanParent());
lastBlockTimestamp_ = _params.lastBlockTimestamp == 0
? uint64(block.timestamp)
: _params.lastBlockTimestamp;

updatedParams_.timestamp = _params.timestamp;
}
require(lastBlockTimestamp_ <= block.timestamp, TimestampTooLarge());

uint256 maxTimestamp = _params.timestamp;
uint64 firstBlockTimestamp = lastBlockTimestamp_;
for (uint256 i; i < _params.blocks.length; ++i) {
maxTimestamp += _params.blocks[i].timeShift;
firstBlockTimestamp -= _params.blocks[i].timeShift;
}
require(maxTimestamp <= block.timestamp, TimestampTooLarge());

// Check if parent batch has the right meta hash. This is to allow the proposer to
require(
firstBlockTimestamp + _maxAnchorHeightOffset * LibNetwork.ETHEREUM_BLOCK_TIME
>= block.timestamp,
TimestampTooSmall()
);

require(
firstBlockTimestamp >= _lastBatch.lastBlockTimestamp, TimestampSmallerThanParent()
);

// make sure the batch builds on the expected latest chain state.
require(
_params.parentMetaHash == 0 || _params.parentMetaHash == _lastBatch.metaHash,
Expand All @@ -669,11 +667,6 @@ abstract contract TaikoInbox is EssentialContract, ITaikoInbox, ITaiko, IFork {

// Memory-only structs ----------------------------------------------------------------------

struct UpdatedParams {
uint64 anchorBlockId;
uint64 timestamp;
}

struct SyncBlock {
uint64 batchId;
uint64 blockId;
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/test/layer1/based/InboxTestBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ abstract contract InboxTestBase is Layer1Test {
console2.log(unicode"|─── batch#", batch.batchId);
}
console2.log(unicode"│ |── metahash:", Strings.toHexString(uint256(batch.metaHash)));
console2.log(unicode"│ |── timestamp:", batch.timestamp);
console2.log(unicode"│ |── lastBlockTimestamp:", batch.lastBlockTimestamp);
console2.log(unicode"│ |── lastBlockId:", batch.lastBlockId);
console2.log(unicode"│ |── anchorBlockId:", batch.anchorBlockId);
console2.log(unicode"│ |── nextTransitionId:", batch.nextTransitionId);
Expand Down
21 changes: 20 additions & 1 deletion packages/protocol/test/layer1/based/InboxTest_Params.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,28 @@ contract InboxTest_Params is InboxTestBase {
function test_validateParams_reverts_when_timestamp_too_large() external transactBy(Alice) {
ITaikoInbox.BatchParams memory params;
params.blocks = new ITaikoInbox.BlockParams[](1);
params.timestamp = uint64(block.timestamp + 1);
params.lastBlockTimestamp = uint64(block.timestamp + 1);

vm.expectRevert(ITaikoInbox.TimestampTooLarge.selector);
inbox.proposeBatch(abi.encode(params), "txList");
}

function test_validateParams_reverts_when_timestamp_smaller_than_parent()
external
transactBy(Alice)
{
ITaikoInbox.BatchParams memory params;
params.blocks = new ITaikoInbox.BlockParams[](1);
params.lastBlockTimestamp = uint64(block.timestamp);
inbox.proposeBatch(abi.encode(params), "txList");

params.lastBlockTimestamp = uint64(block.timestamp - 1);
vm.expectRevert(ITaikoInbox.TimestampSmallerThanParent.selector);
inbox.proposeBatch(abi.encode(params), "txList");

params.blocks[0].timeShift = 1;
params.lastBlockTimestamp = uint64(block.timestamp);
vm.expectRevert(ITaikoInbox.TimestampSmallerThanParent.selector);
inbox.proposeBatch(abi.encode(params), "txList");
}
}
24 changes: 12 additions & 12 deletions packages/protocol/test/layer1/based/InboxTest_ProposeAndProve.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand Down Expand Up @@ -97,7 +97,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand All @@ -108,7 +108,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.anchorBlockId, block.number - 1);
assertEq(batch.nextTransitionId, 1);
assertEq(batch.verifiedTransitionId, 0);
Expand Down Expand Up @@ -154,7 +154,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand All @@ -165,7 +165,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.anchorBlockId, block.number - 1);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 0);
Expand Down Expand Up @@ -229,7 +229,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand All @@ -240,7 +240,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.anchorBlockId, block.number - 1);
assertEq(batch.nextTransitionId, 2);
if (i % getConfig().stateRootSyncInternal == 0 || i == stats2.lastVerifiedBatchId) {
Expand Down Expand Up @@ -290,7 +290,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand All @@ -301,7 +301,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.lastBlockId, i * 7);
assertEq(batch.anchorBlockId, block.number - 1);
assertEq(batch.nextTransitionId, 2);
Expand Down Expand Up @@ -338,7 +338,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
ITaikoInbox.Batch memory batch = inbox.getBatch(0);
assertEq(batch.batchId, 0);
assertEq(batch.metaHash, bytes32(uint256(1)));
assertEq(batch.timestamp, genesisBlockProposedAt);
assertEq(batch.lastBlockTimestamp, genesisBlockProposedAt);
assertEq(batch.anchorBlockId, genesisBlockProposedIn);
assertEq(batch.nextTransitionId, 2);
assertEq(batch.verifiedTransitionId, 1);
Expand All @@ -349,7 +349,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.anchorBlockId, block.number - 1);
assertEq(batch.nextTransitionId, 3);
if (i % getConfig().stateRootSyncInternal == 0 || i == stats2.lastVerifiedBatchId) {
Expand Down Expand Up @@ -404,7 +404,7 @@ contract InboxTest_ProposeAndProve is InboxTestBase {
assertEq(batch.batchId, i);
assertEq(batch.metaHash, keccak256(abi.encode(_loadMetadata(i))));

assertEq(batch.timestamp, block.timestamp);
assertEq(batch.lastBlockTimestamp, block.timestamp);
assertEq(batch.anchorBlockId, block.number - 1);
if (i == 8) {
assertEq(batch.verifiedTransitionId, 0);
Expand Down

0 comments on commit 5d28e03

Please sign in to comment.