diff --git a/.github/workflows/ci-surge.yml b/.github/workflows/ci-surge.yml index 87ff8b8c468..96f2d43376b 100644 --- a/.github/workflows/ci-surge.yml +++ b/.github/workflows/ci-surge.yml @@ -18,7 +18,7 @@ jobs: && !startsWith(github.head_ref, 'release-please') }} name: Integration tests runs-on: [ubuntu-latest] - timeout-minutes: 45 + timeout-minutes: 20 env: SURGE_TAIKO_MONO_DIR: surge-taiko-mono PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono @@ -149,6 +149,7 @@ jobs: fi - name: Run integration tests + timeout-minutes: 15 working-directory: >- ${{ env.SURGE_TAIKO_MONO_DIR }}/packages/taiko-client env: diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml index 2d92a38090a..8324e12b253 100644 --- a/.github/workflows/ci-taiko.yml +++ b/.github/workflows/ci-taiko.yml @@ -18,7 +18,7 @@ jobs: && !startsWith(github.head_ref, 'release-please') }} name: Integration tests runs-on: [ubuntu-latest] - timeout-minutes: 45 + timeout-minutes: 20 env: TAIKO_MONO_DIR: taiko-mono PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono @@ -149,6 +149,7 @@ jobs: fi - name: Run integration tests + timeout-minutes: 15 working-directory: >- ${{ env.TAIKO_MONO_DIR }}/packages/taiko-client env: diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs index aba6ba96d1f..856dfa0cb57 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ForkchoiceUpdatedHandler.cs @@ -288,12 +288,15 @@ protected virtual bool IsOnMainChainBehindHead(Block newHeadBlock, ForkchoiceSta return null; } + protected virtual bool IsPayloadTimestampValid(Block newHeadBlock, PayloadAttributes payloadAttributes) + => payloadAttributes.Timestamp > newHeadBlock.Timestamp; + protected bool ArePayloadAttributesTimestampAndSlotNumberValid(Block newHeadBlock, ForkchoiceStateV1 forkchoiceState, PayloadAttributes payloadAttributes, [NotNullWhen(false)] out ResultWrapper? errorResult) { - if (newHeadBlock.Timestamp >= payloadAttributes.Timestamp) + if (!IsPayloadTimestampValid(newHeadBlock, payloadAttributes)) { - string error = $"Payload timestamp {payloadAttributes.Timestamp} must be greater than block timestamp {newHeadBlock.Timestamp}."; + string error = $"Invalid payload timestamp {payloadAttributes.Timestamp} for block timestamp {newHeadBlock.Timestamp}."; errorResult = ForkchoiceUpdatedV1Result.Error(error, MergeErrorCodes.InvalidPayloadAttributes); return false; } diff --git a/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs index eb9a581d29b..5dea7331076 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs @@ -3,11 +3,13 @@ using NUnit.Framework; using Nethermind.Core; +using Nethermind.Consensus.Producers; using Nethermind.Merge.Plugin.Handlers; using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Consensus.Processing; using Nethermind.Consensus; +using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Logging; using Nethermind.Merge.Plugin.BlockProduction; @@ -68,4 +70,56 @@ static void AddBlock(IBlockTree blockTree, Block block) blockTree.HeadHash.Returns(block.Hash!); } } + + [TestCase(100ul, 100ul, true, TestName = "Equal timestamps allowed for Pacaya")] + [TestCase(100ul, 101ul, true, TestName = "Greater timestamp allowed")] + [TestCase(100ul, 99ul, false, TestName = "Lesser timestamp rejected")] + public async Task Test_ForkchoiceUpdatedHandler_Allows_Equal_Timestamps(ulong headTimestamp, ulong payloadTimestamp, bool shouldSucceed) + { + IBlockTree blockTree = Substitute.For(); + + Block headBlock = Build.A.Block.WithNumber(1).WithTimestamp(headTimestamp).TestObject; + + blockTree.FindBlock(headBlock.Hash!, BlockTreeLookupOptions.DoNotCreateLevelIfMissing).Returns(headBlock); + blockTree.GetInfo(headBlock.Number, headBlock.Hash!).Returns((new BlockInfo(headBlock.Hash!, 0) { WasProcessed = true }, new ChainLevelInfo(true))); + blockTree.Head.Returns(headBlock); + blockTree.HeadHash.Returns(headBlock.Hash!); + blockTree.IsMainChain(headBlock.Header).Returns(true); + + TaikoForkchoiceUpdatedHandler handler = new( + blockTree, + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + Substitute.For(), + new MergeConfig(), + Substitute.For() + ); + + PayloadAttributes payloadAttributes = new() + { + Timestamp = payloadTimestamp, + PrevRandao = Keccak.Zero, + SuggestedFeeRecipient = Address.Zero + }; + + ForkchoiceStateV1 forkchoiceState = new(headBlock.Hash!, headBlock.Hash!, headBlock.Hash!); + ResultWrapper result = await handler.Handle(forkchoiceState, payloadAttributes, 1); + + if (shouldSucceed) + { + Assert.That(result.Data.PayloadStatus.Status, Is.EqualTo(PayloadStatus.Valid)); + } + else + { + Assert.That(result.Result.Error, Does.Contain("Invalid payload timestamp")); + } + } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs index fd86986f596..5cc5ef6b4e9 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs @@ -77,6 +77,11 @@ protected override bool IsOnMainChainBehindHead(Block newHeadBlock, ForkchoiceSt return blockHeader; } + // Taiko allows equal timestamps because multiple L2 blocks can be derived + // from a single L1 block, all sharing the same L1 anchor timestamp. + protected override bool IsPayloadTimestampValid(Block newHeadBlock, PayloadAttributes payloadAttributes) + => payloadAttributes.Timestamp >= newHeadBlock.Timestamp; + protected override bool TryGetBranch(Block newHeadBlock, out Block[] blocks) { // Allow resetting to any block already on the main chain (including genesis)