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;