Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using Nethermind.Core.Crypto;
using Nethermind.Core.Test.Builders;
using Nethermind.Core.Extensions;
using Nethermind.Serialization.Rlp;
using NUnit.Framework;

Expand Down Expand Up @@ -219,4 +220,88 @@ private static TxReceipt CreateStorageFrameReceipt(LogEntry[] unionLogs, params

private static LogEntry Log(byte marker) =>
new(TestItem.AddressB, [marker], [Keccak.Compute([marker])]);

private const string OldSingleNonCompactHex =
"f90207b9020406f90200018080809476e68a8696537e4141926f3e528733af9e237d6980808082c738b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000002000000000000000000000000000000080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd201ff808094b7705ae4c6f81b66cdb323c65f4e8133690fc099f888f84001825208f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd201f84080827530f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a0f2ee15ea639b73fa3db9b34a245bdfa015c260c598b211bf05a1ecc4b3e3b4f202c30280c0";
private const string OldSingleCompactHex =
"7ff8f8f8f7019476e68a8696537e4141926f3e528733af9e237d6982c738f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2800194b7705ae4c6f81b66cdb323c65f4e8133690fc099f88af84101825208f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd28001f84180827530f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a0f2ee15ea639b73fa3db9b34a245bdfa015c260c598b211bf05a1ecc4b3e3b4f28002c30280c0";
private const string OldArrayNonCompactHex =
"f905a5f901ce01a0017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72020294475674cb523a0a2736b7f7534390288fce16982c94942921b14f1b1c385cd7e0cc2ef7abe5598c83589476e68a8696537e4141926f3e528733af9e237d69648203e8b9010000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000f83af838940000000000000000000000000000000000000000e1a0000000000000000000000000000000000000000000000000000000000000000080ffa003783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760856572726f72b9020006f901fc018080809476e68a8696537e4141926f3e528733af9e237d6980808082c738b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000002000000000000000000000000000000080000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd201ff808094b7705ae4c6f81b66cdb323c65f4e8133690fc099f884f84001825208f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd201f84080827530f83af83894942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a0f2ee15ea639b73fa3db9b34a245bdfa015c260c598b211bf05a1ecc4b3e3b4f202f901ce01a0017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f720202942d36e6c27c34ea22620e7b7c45de774599406cf394942921b14f1b1c385cd7e0cc2ef7abe5598c83589476e68a8696537e4141926f3e528733af9e237d69648207d0b9010000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000f83af838940000000000000000000000000000000000000000e1a0000000000000000000000000000000000000000000000000000000000000000080ffa003783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760856572726f72";
private const string OldArrayCompactHex =
"7ff9015ef40194475674cb523a0a2736b7f7534390288fce16982c8203e8dad9940000000000000000000000000000000000000000c1008080f8f3019476e68a8696537e4141926f3e528733af9e237d6982c738f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd2800194b7705ae4c6f81b66cdb323c65f4e8133690fc099f886f84101825208f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a05fe7f977e71dba2ea1a68e21057beebb9be2ac30c6410aa38d4f3fbe41dcffd28001f84180827530f83bf83994942921b14f1b1c385cd7e0cc2ef7abe5598c8358e1a0f2ee15ea639b73fa3db9b34a245bdfa015c260c598b211bf05a1ecc4b3e3b4f28002f401942d36e6c27c34ea22620e7b7c45de774599406cf38207d0dad9940000000000000000000000000000000000000000c1008080";

[Test]
public void StorageDecode_PreTwoDimensionalScalarGasUsed_ReadsExecutionWithZeroState(
[Values(false, true)] bool compact)
{
byte[] encoded = Bytes.FromHexString(compact ? OldSingleCompactHex : OldSingleNonCompactHex);
RlpReader ctx = new(encoded);
TxReceipt[] decoded = ReceiptArrayStorageDecoder.Instance.Decode(ref ctx, RlpBehaviors.Storage);

Assert.That(decoded, Has.Length.EqualTo(1));
TxReceipt receipt = decoded[0];
Assert.That(receipt.TxType, Is.EqualTo(TxType.FrameTx));
Assert.That(receipt.GasUsedTotal, Is.EqualTo(51_000UL));
Assert.That(receipt.Payer, Is.EqualTo(TestItem.AddressA));
AssertFrameReceiptsEqual(receipt.FrameReceipts!,
[
new TxFrameReceipt(TxFrameReceipt.StatusSuccess, 21_000, 0, [Log(0x01)]),
new TxFrameReceipt(TxFrameReceipt.StatusFailure, 30_000, 0, [Log(0x02)]),
new TxFrameReceipt(TxFrameReceipt.StatusSkipped, 0, 0, []),
]);
}

[Test]
public void StorageDecode_PreTwoDimensionalFrameReceiptBetweenRegulars_DecodesAllAndStaysAligned(
[Values(false, true)] bool compact)
{
byte[] encoded = Bytes.FromHexString(compact ? OldArrayCompactHex : OldArrayNonCompactHex);
RlpReader ctx = new(encoded);
TxReceipt[] decoded = ReceiptArrayStorageDecoder.Instance.Decode(ref ctx, RlpBehaviors.Storage);

Assert.That(decoded, Has.Length.EqualTo(3));
Assert.That(decoded[0].Sender, Is.EqualTo(TestItem.AddressD), "leading regular receipt sender");
Assert.That(decoded[0].GasUsedTotal, Is.EqualTo(1000UL), "leading regular receipt gas used total");
Assert.That(decoded[0].TxType, Is.EqualTo(TxType.Legacy));

Assert.That(decoded[1].TxType, Is.EqualTo(TxType.FrameTx));
AssertFrameReceiptsEqual(decoded[1].FrameReceipts!,
[
new TxFrameReceipt(TxFrameReceipt.StatusSuccess, 21_000, 0, [Log(0x01)]),
new TxFrameReceipt(TxFrameReceipt.StatusFailure, 30_000, 0, [Log(0x02)]),
]);

Assert.That(decoded[2].Sender, Is.EqualTo(TestItem.AddressE), "trailing regular receipt sender");
Assert.That(decoded[2].GasUsedTotal, Is.EqualTo(2000UL), "trailing regular receipt gas used total");
Assert.That(decoded[2].TxType, Is.EqualTo(TxType.Legacy));
}

[Test]
public void StructRefIteration_OverPreTwoDimensionalArray_DoesNotThrowOrCorruptNeighbours(
[Values(false, true)] bool compact)
{
byte[] encoded = Bytes.FromHexString(compact ? OldArrayCompactHex : OldArrayNonCompactHex);
IReceiptRefDecoder refDecoder = compact
? new CompactReceiptStorageDecoder()
: (IReceiptRefDecoder)new ReceiptStorageDecoder();

ReadOnlySpan<byte> body = ReceiptArrayStorageDecoder.IsCompactEncoding(encoded) ? encoded.AsSpan(1) : encoded;
RlpReader reader = new(body);
int length = reader.ReadSequenceLength();
int count = 0;
(string Sender, ulong Gas, TxType Type)[] seen = new (string, ulong, TxType)[3];
while (reader.Position < length)
{
refDecoder.DecodeStructRef(ref reader, RlpBehaviors.Storage, out TxReceiptStructRef current);
if (count < seen.Length)
{
seen[count] = (current.Sender.ToString(), current.GasUsedTotal, current.TxType);
}
count++;
}

Assert.That(count, Is.EqualTo(3), "every receipt must decode, including the neighbours");
Assert.That(seen[0], Is.EqualTo((TestItem.AddressD.ToString(), 1000UL, TxType.Legacy)));
Assert.That(seen[2], Is.EqualTo((TestItem.AddressE.ToString(), 2000UL, TxType.Legacy)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,7 @@ private static TxFrameReceipt[] DecodeFrameReceipts(ref RlpReader decoderContext
{
int frameEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
byte status = decoderContext.DecodeByte();
int gasUsedEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
ulong executionGasUsed = decoderContext.DecodeULong();
ulong stateGasUsed = decoderContext.DecodeULong();
decoderContext.Check(gasUsedEnd);
FrameReceiptGasRlp.DecodeGasUsed(ref decoderContext, out ulong executionGasUsed, out ulong stateGasUsed);

int logsEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
using ArrayPoolListRef<LogEntry> frameLogs = new(4);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

namespace Nethermind.Serialization.Rlp;

internal static class FrameReceiptGasRlp
{
/// <summary>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trim comment

/// Decodes a stored frame receipt's <c>gas_used</c>, tolerating both the current
/// <c>[execution, state]</c> list and the pre-two-dimensional scalar written by
/// <c>eip8141-frame-txs-devnet7</c>.
/// </summary>
/// <remarks>
/// A <see cref="ulong"/> RLP scalar is always below the sequence prefix range, so the list is
/// unambiguously distinguished from a scalar. The scalar path attributes the whole value to the
/// execution dimension (<c>state = 0</c>): it preserves the per-frame total (and therefore
/// <c>GasUsedTotal</c> and the JSON-RPC <c>gasUsed</c>), but the receipt is not wire-faithful when
/// re-encoded for <c>GetReceipts</c>, where the split is carried on the wire. Read-compatibility
/// only, removable once no node holds devnet7-era frame-tx receipts on disk.
/// </remarks>
public static void DecodeGasUsed(ref RlpReader reader, out ulong execution, out ulong state)
{
if (reader.IsSequenceNext())
{
int gasUsedEnd = reader.ReadSequenceLength() + reader.Position;
execution = reader.DecodeULong();
state = reader.DecodeULong();
reader.Check(gasUsedEnd);
}
else
{
execution = reader.DecodeULong();
state = 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,7 @@ private static TxFrameReceipt[] DecodeFrameReceipts(ref RlpReader decoderContext
{
int frameEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
byte status = decoderContext.DecodeByte();
int gasUsedEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
ulong executionGasUsed = decoderContext.DecodeULong();
ulong stateGasUsed = decoderContext.DecodeULong();
decoderContext.Check(gasUsedEnd);
FrameReceiptGasRlp.DecodeGasUsed(ref decoderContext, out ulong executionGasUsed, out ulong stateGasUsed);

int logsEnd = decoderContext.ReadSequenceLength() + decoderContext.Position;
List<LogEntry> frameLogs = [];
Expand Down
Loading