diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs
index 8ab244b3f565..db9b0c5f275f 100644
--- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs
+++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs
@@ -79,10 +79,9 @@ public class TaikoExtendedEthModule(
}
///
- /// Traverses the blockchain backwards to find the last Shasta block of the given Shasta batch ID.
+ /// Traverses the blockchain backwards to find the last Shasta block of the given batch ID.
///
- /// The batch ID.
- /// The last block ID.
+ /// The Shasta batch identifier for which to find the last corresponding block.
private UInt256? GetLastBlockByBatchId(UInt256 batchId)
{
Block? currentBlock = blockFinder.Head;
@@ -96,8 +95,7 @@ public class TaikoExtendedEthModule(
break;
}
- UInt256? proposalId = ExtractAnchorV4ProposalId(currentBlock.Transactions[0].Data);
-
+ UInt256? proposalId = currentBlock.Header.DecodeShastaProposalID();
if (proposalId is null)
{
return null;
@@ -118,29 +116,4 @@ private static bool HasAnchorV4Prefix(ReadOnlyMemory data)
{
return data.Length >= 4 && AnchorV4Selector.AsSpan().SequenceEqual(data.Span[..4]);
}
-
- private static UInt256? ExtractAnchorV4ProposalId(ReadOnlyMemory data)
- {
- // Calldata layout: 4-byte selector + ABI-encoded arguments.
- // The first 32 bytes hold the offset (relative to args start) where the proposal id is stored.
- const int selectorLength = 4;
- const int dataLength = 32;
-
- if (data.Length <= selectorLength + dataLength)
- {
- return null;
- }
-
- ReadOnlySpan args = data.Span[selectorLength..];
- var offset = new UInt256(args[..dataLength], true);
-
- // Check if the offset is invalid
- if (offset > int.MaxValue || offset + dataLength > args.Length)
- {
- return null;
- }
-
- ReadOnlySpan proposalIdBytes = args.Slice((int)offset, dataLength);
- return new UInt256(proposalIdBytes, true);
- }
}
diff --git a/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs b/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs
index 4b2331e60d33..605262865479 100644
--- a/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs
+++ b/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs
@@ -24,7 +24,7 @@ public class TaikoBlockValidator(
private static readonly byte[] AnchorSelector = Keccak.Compute("anchor(bytes32,bytes32,uint64,uint32)").Bytes[..4].ToArray();
private static readonly byte[] AnchorV2Selector = Keccak.Compute("anchorV2(uint64,bytes32,uint32,(uint8,uint8,uint32,uint64,uint32))").Bytes[..4].ToArray();
private static readonly byte[] AnchorV3Selector = Keccak.Compute("anchorV3(uint64,bytes32,uint32,(uint8,uint8,uint32,uint64,uint32),bytes32[])").Bytes[..4].ToArray();
- public static readonly byte[] AnchorV4Selector = Keccak.Compute("anchorV4((uint48,address,bytes),(uint48,bytes32,bytes32))").Bytes[..4].ToArray();
+ public static readonly byte[] AnchorV4Selector = Keccak.Compute("anchorV4((uint48,bytes32,bytes32))").Bytes[..4].ToArray();
public static readonly Address GoldenTouchAccount = new("0x0000777735367b36bC9B61C50022d9D0700dB4Ec");
diff --git a/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs b/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs
index 1f73b6a20a69..8b4a63f9e996 100644
--- a/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs
+++ b/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs
@@ -3,18 +3,44 @@
using System;
using Nethermind.Core;
+using Nethermind.Int256;
namespace Nethermind.Taiko;
+///
+/// Helper methods for decoding Taiko block header extraData.
+/// Shasta extraData layout: [basefeeSharingPctg (1 byte)][proposalId (6 bytes)]
+///
internal static class TaikoHeaderHelper
{
- public const int ShastaExtraDataMinLen = 2;
+ public const int ShastaExtraDataBasefeeSharingPctgIndex = 0;
+ public const int ShastaExtraDataProposalIDIndex = 1;
+ public const int ShastaExtraDataProposalIDLength = 6;
+ public const int ShastaExtraDataLen = 1 + ShastaExtraDataProposalIDLength;
- public static byte? DecodeOntakeExtraData(this BlockHeader header) => header.ExtraData is { Length: >= 32 } ? Math.Min(header.ExtraData[31], (byte)100) : null;
+ ///
+ /// Decodes Ontake/Pacaya extraData to get basefeeSharingPctg (last byte, capped at 100).
+ ///
+ public static byte? DecodeOntakeExtraData(this BlockHeader header) =>
+ header.ExtraData is { Length: >= 32 } ? Math.Min(header.ExtraData[31], (byte)100) : null;
- // Two bytes encoded in the extra data for Shasta
- // - First byte: basefeeSharingPctg
- // - Second byte: isLowBondProposal (lowest bit)
- // Returns only the basefeeSharingPctg
- public static byte? DecodeShastaExtraData(this BlockHeader header) => header.ExtraData is { Length: < ShastaExtraDataMinLen } ? null : header.ExtraData[0];
+ ///
+ /// Decodes Shasta extraData to get basefeeSharingPctg (first byte).
+ ///
+ public static byte? DecodeShastaBasefeeSharingPctg(this BlockHeader header) =>
+ header.ExtraData is { Length: < ShastaExtraDataLen } ? null : header.ExtraData[ShastaExtraDataBasefeeSharingPctgIndex];
+
+ ///
+ /// Decodes Shasta extraData to get proposalId (bytes 1-6).
+ ///
+ public static UInt256? DecodeShastaProposalID(this BlockHeader header)
+ {
+ if (header.ExtraData is null || header.ExtraData.Length < ShastaExtraDataLen)
+ {
+ return null;
+ }
+
+ ReadOnlySpan proposalIdBytes = header.ExtraData.AsSpan(ShastaExtraDataProposalIDIndex, ShastaExtraDataProposalIDLength);
+ return new UInt256(proposalIdBytes, true);
+ }
}
diff --git a/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs b/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs
index b26cdbb1ff86..72e481345eb9 100644
--- a/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs
+++ b/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs
@@ -60,9 +60,9 @@ protected override bool ValidateExtraData(BlockHeader header, IReleaseSpec spec,
{
var taikoSpec = (ITaikoReleaseSpec)spec;
- if (taikoSpec.IsShastaEnabled && header.ExtraData is { Length: < TaikoHeaderHelper.ShastaExtraDataMinLen })
+ if (taikoSpec.IsShastaEnabled && header.ExtraData is { Length: < TaikoHeaderHelper.ShastaExtraDataLen })
{
- error = $"ExtraData must be at least {TaikoHeaderHelper.ShastaExtraDataMinLen} bytes for Shasta, but got {header.ExtraData.Length}";
+ error = $"ExtraData must be at least {TaikoHeaderHelper.ShastaExtraDataLen} bytes for Shasta, but got {header.ExtraData.Length}";
if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - {error}");
return false;
}
@@ -93,7 +93,7 @@ private bool ValidateEip4396Header(BlockHeader header, BlockHeader parent, IRele
ulong parentBlockTime = 0;
if (header.Number > 1)
{
- BlockHeader? grandParent = _blockTree?.FindHeader(parent.ParentHash!, BlockTreeLookupOptions.None);
+ BlockHeader? grandParent = _blockTree?.FindHeader(parent.ParentHash!, BlockTreeLookupOptions.None, blockNumber: parent.Number - 1);
if (grandParent is null)
{
error = $"Ancestor block not found for parent {parent.ParentHash}";
diff --git a/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs b/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs
index 11b6fa4962f8..fc8e49720380 100644
--- a/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs
+++ b/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs
@@ -63,7 +63,7 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec
var taikoSpec = (ITaikoReleaseSpec)spec;
if (taikoSpec.IsOntakeEnabled || taikoSpec.IsShastaEnabled)
{
- byte basefeeSharingPct = (taikoSpec.IsShastaEnabled ? header.DecodeShastaExtraData() : header.DecodeOntakeExtraData()) ?? 0;
+ byte basefeeSharingPct = (taikoSpec.IsShastaEnabled ? header.DecodeShastaBasefeeSharingPctg() : header.DecodeOntakeExtraData()) ?? 0;
UInt256 feeCoinbase = baseFees * basefeeSharingPct / 100;