Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update config, gas calculations, receipt fields according to OP's Fjord #7129

Merged
merged 14 commits into from
Jun 7, 2024
2 changes: 2 additions & 0 deletions src/Nethermind/Chains/op-sepolia.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"bedrockBlockNumber": "0x0",
"canyonTimestamp": "0x6553a790",
"ecotoneTimestamp": "0x65D62C10",
"fjordTimestamp": "0x66575100",
"l1FeeRecipient": "0x420000000000000000000000000000000000001A",
"l1BlockAddress": "0x4200000000000000000000000000000000000015",
"canyonBaseFeeChangeDenominator": "250",
Expand Down Expand Up @@ -60,6 +61,7 @@
"eip4844TransitionTimestamp": "0x65D62C10",
"eip5656TransitionTimestamp": "0x65D62C10",
"eip6780TransitionTimestamp": "0x65D62C10",
"eip7212TransitionTimestamp": "0x66575100",
"terminalTotalDifficulty": "0"
},
"genesis": {
Expand Down
37 changes: 37 additions & 0 deletions src/Nethermind/Nethermind.Optimism.Test/GasCostTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Collections;
using Nethermind.Int256;
using NUnit.Framework;

namespace Nethermind.Optimism.Test;

public class GasCostTests
{
[TestCaseSource(nameof(FjordL1CostCalculationTestCases))]
public UInt256 Fjord_l1cost_should_match(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar) =>
OPL1CostHelper.ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar);

public static IEnumerable FjordL1CostCalculationTestCases
{
get
{
static TestCaseData MakeTestCase(string testCase, ulong result, ulong fastLzSize, ulong l1BaseFee, ulong blobBaseFee, ulong l1BaseFeeScalar, ulong l1BlobBaseFeeScalar)
{
return new TestCaseData(new UInt256(fastLzSize), new UInt256(l1BaseFee), new UInt256(blobBaseFee), new UInt256(l1BaseFeeScalar), new UInt256(l1BlobBaseFeeScalar))
{
ExpectedResult = new UInt256(result),
TestName = testCase
};
}

yield return MakeTestCase("Low compressed size", 3203000, 50, 1000000000, 10000000, 2, 3);
yield return MakeTestCase("Below minimal #1", 3203000, 150, 1000000000, 10000000, 2, 3);
yield return MakeTestCase("Below minimal #2", 3203000, 170, 1000000000, 10000000, 2, 3);
yield return MakeTestCase("Above minimal #1", 3217602, 171, 1000000000, 10000000, 2, 3);
yield return MakeTestCase("Above minimal #2", 3994602, 200, 1000000000, 10000000, 2, 3);
yield return MakeTestCase("Regular block #1", 2883950646753, 1044, 28549556977, 1, 7600, 862000);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Nethermind.Optimism.Test;

public partial class ReceiptDecoderTests
public class ReceiptDecoderTests
{
[TestCaseSource(nameof(DepositTxReceiptsSerializationTestCases))]
public void Test_tx_network_form_receipts_properly_encoded_for_trie(byte[] rlp, bool includesNonce, bool includesVersion, bool shouldIncludeNonceAndVersionForTxTrie)
Expand Down
1 change: 1 addition & 0 deletions src/Nethermind/Nethermind.Optimism/IOptimismSpecHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface IOptimismSpecHelper
bool IsRegolith(BlockHeader header);
bool IsCanyon(BlockHeader header);
bool IsEcotone(BlockHeader header);
bool IsFjord(BlockHeader header);
Address? Create2DeployerAddress { get; }
byte[]? Create2DeployerCode { get; }
}
20 changes: 15 additions & 5 deletions src/Nethermind/Nethermind.Optimism/L1BlockGasInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ public readonly struct L1BlockGasInfo
private readonly UInt256 _overhead;
private readonly UInt256 _feeScalar;
private readonly string? _feeScalarDecimal;
private readonly bool _isFjord;
private readonly bool _isEcotone;
private readonly bool _isRegolith;

private static readonly byte[] EcotoneL1AttributesSelector = [0x44, 0x0a, 0x5e, 0x20];
private static readonly byte[] BedrockL1AttributesSelector = [0x01, 0x5d, 0x8e, 0xb9];
private readonly IOptimismSpecHelper _specHelper;

public L1BlockGasInfo(Block block, bool isRegolith)
public L1BlockGasInfo(Block block, IOptimismSpecHelper specHelper)
{
_isRegolith = isRegolith;
_specHelper = specHelper;

if (block is not null && block.Transactions.Length > 0)
{
Expand All @@ -45,7 +47,9 @@ public L1BlockGasInfo(Block block, bool isRegolith)

Memory<byte> data = depositTx.Data.Value;

if (_isEcotone = data[0..4].Span.SequenceEqual(EcotoneL1AttributesSelector))
_isFjord = _specHelper.IsFjord(block.Header);

if (_isFjord || (_isEcotone = (_specHelper.IsEcotone(block.Header) && !data[0..4].Span.SequenceEqual(BedrockL1AttributesSelector))))
{
if (data.Length != 164)
{
Expand All @@ -59,6 +63,7 @@ public L1BlockGasInfo(Block block, bool isRegolith)
}
else
{
_isRegolith = true;
if (data.Length < 4 + 32 * 8)
{
return;
Expand All @@ -80,7 +85,12 @@ public readonly L1TxGasInfo GetTxGasInfo(Transaction tx)

if (_l1GasPrice is not null)
{
if (_isEcotone)
if (_isFjord)
{
UInt256 fastLzSize = OPL1CostHelper.ComputeFlzCompressLen(tx);
l1Fee = OPL1CostHelper.ComputeL1CostFjord(fastLzSize, _l1GasPrice.Value, _l1BlobBaseFee, _l1BaseFeeScalar, _l1BlobBaseFeeScalar);
}
else if (_isEcotone)
{
l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isRegolith);
l1Fee = OPL1CostHelper.ComputeL1CostEcotone(l1GasUsed.Value, _l1GasPrice.Value, _l1BlobBaseFee, _l1BaseFeeScalar, _l1BlobBaseFeeScalar);
Expand Down
6 changes: 6 additions & 0 deletions src/Nethermind/Nethermind.Optimism/OPConfigHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class OptimismSpecHelper(OptimismParameters parameters) : IOptimismSpecHe
private readonly ulong _regolithTimestamp = parameters.RegolithTimestamp;
private readonly ulong? _canyonTimestamp = parameters.CanyonTimestamp;
private readonly ulong? _ecotoneTimestamp = parameters.EcotoneTimestamp;
private readonly ulong? _fjordTimestamp = parameters.FjordTimestamp;

public Address L1FeeReceiver { get; init; } = parameters.L1FeeRecipient;

Expand All @@ -35,6 +36,11 @@ public bool IsEcotone(BlockHeader header)
return header.Timestamp >= _ecotoneTimestamp;
}

public bool IsFjord(BlockHeader header)
{
return header.Timestamp >= _fjordTimestamp;
}

public Address? Create2DeployerAddress { get; } = parameters.Create2DeployerAddress;
public byte[]? Create2DeployerCode { get; } = parameters.Create2DeployerCode;
}
174 changes: 167 additions & 7 deletions src/Nethermind/Nethermind.Optimism/OPL1CostHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers;
using System.Linq;
using System.Runtime.CompilerServices;
using Nethermind.Core;
Expand All @@ -20,27 +21,55 @@ public class OPL1CostHelper(IOptimismSpecHelper opSpecHelper, Address l1BlockAdd
private readonly StorageCell _overheadSlot = new(l1BlockAddr, new UInt256(5));
private readonly StorageCell _scalarSlot = new(l1BlockAddr, new UInt256(6));

private static readonly UInt256 basicDevider = 1_000_000;
private static readonly UInt256 BasicDivisor = 1_000_000;

// Ecotone
private readonly StorageCell _blobBaseFeeSlot = new(l1BlockAddr, new UInt256(7));
private readonly StorageCell _baseFeeScalarSlot = new(l1BlockAddr, new UInt256(3));

private static readonly UInt256 precisionMultiplier = 16;
private static readonly UInt256 precisionDevider = precisionMultiplier * basicDevider;
private static readonly UInt256 PrecisionMultiplier = 16;
private static readonly UInt256 PrecisionDivisor = PrecisionMultiplier * BasicDivisor;


// Fjord
private static readonly UInt256 L1CostInterceptNeg = 42_585_600;
private static readonly UInt256 L1CostFastlzCoef = 836_500;

private static readonly UInt256 MinTransactionSizeScaled = 100 * 1_000_000;
private static readonly UInt256 FjordDivisor = 1_000_000_000_000;

[SkipLocalsInit]
public UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState worldState)
{
if (tx.IsDeposit())
return UInt256.Zero;

UInt256 l1BaseFee = new(worldState.Get(_l1BaseFeeSlot), true);

if (_opSpecHelper.IsFjord(header))
{
UInt256 blobBaseFee = new(worldState.Get(_blobBaseFeeSlot), true);

ReadOnlySpan<byte> scalarData = worldState.Get(_baseFeeScalarSlot);

const int baseFeeFieldsStart = 16;
const int fieldSize = sizeof(uint);

int l1BaseFeeScalarStart = scalarData.Length > baseFeeFieldsStart ? scalarData.Length - baseFeeFieldsStart : 0;
int l1BaseFeeScalarEnd = l1BaseFeeScalarStart + (scalarData.Length >= baseFeeFieldsStart ? fieldSize : fieldSize - baseFeeFieldsStart + scalarData.Length);
UInt256 l1BaseFeeScalar = new(scalarData[l1BaseFeeScalarStart..l1BaseFeeScalarEnd], true);
UInt256 l1BlobBaseFeeScalar = new(scalarData[l1BaseFeeScalarEnd..(l1BaseFeeScalarEnd + fieldSize)], true);

uint fastLzSize = ComputeFlzCompressLen(tx);

return ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar);
}

UInt256 dataGas = ComputeDataGas(tx, _opSpecHelper.IsRegolith(header));

if (dataGas.IsZero)
return UInt256.Zero;

UInt256 l1BaseFee = new(worldState.Get(_l1BaseFeeSlot), true);

if (_opSpecHelper.IsEcotone(header))
{
UInt256 blobBaseFee = new(worldState.Get(_blobBaseFeeSlot), true);
Expand Down Expand Up @@ -79,15 +108,146 @@ public static UInt256 ComputeDataGas(Transaction tx, bool isRegolith)
return (ulong)(zeroCount * GasCostOf.TxDataZero + nonZeroCount * GasCostOf.TxDataNonZeroEip2028);
}

// Fjord L1 formula:
// l1FeeScaled = baseFeeScalar * l1BaseFee * 16 + blobFeeScalar * l1BlobBaseFee
// estimatedSize = max(minTransactionSize, intercept + fastlzCoef * fastlzSize)
// l1Cost = estimatedSize * l1FeeScaled / 1e12
public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar)
{
UInt256 l1FeeScaled = l1BaseFeeScalar * l1BaseFee * PrecisionMultiplier + l1BlobBaseFeeScalar * blobBaseFee;
UInt256 fastLzCost = L1CostFastlzCoef * fastLzSize;

if (fastLzCost < L1CostInterceptNeg)
{
fastLzCost = 0;
}
else
{
fastLzCost -= L1CostInterceptNeg;
}

var estimatedSize = UInt256.Max(MinTransactionSizeScaled, fastLzCost);
return estimatedSize * l1FeeScaled / FjordDivisor;
}

// Ecotone formula: (dataGas) * (16 * l1BaseFee * l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar) / 16e6
public static UInt256 ComputeL1CostEcotone(UInt256 dataGas, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar)
{
return dataGas * (precisionMultiplier * l1BaseFee * l1BaseFeeScalar + blobBaseFee * l1BlobBaseFeeScalar) / precisionDevider;
return dataGas * (PrecisionMultiplier * l1BaseFee * l1BaseFeeScalar + blobBaseFee * l1BlobBaseFeeScalar) / PrecisionDivisor;
}

// Pre-Ecotone formula: (dataGas + overhead) * l1BaseFee * scalar / 1e6
public static UInt256 ComputeL1CostPreEcotone(UInt256 dataGasWithOverhead, UInt256 l1BaseFee, UInt256 feeScalar)
{
return dataGasWithOverhead * l1BaseFee * feeScalar / basicDevider;
return dataGasWithOverhead * l1BaseFee * feeScalar / BasicDivisor;
}

// Based on:
// https://github.com/ethereum-optimism/op-geth/blob/7c2819836018bfe0ca07c4e4955754834ffad4e0/core/types/rollup_cost.go
// https://github.com/Vectorized/solady/blob/5315d937d79b335c668896d7533ac603adac5315/js/solady.js
[SkipLocalsInit]
public static uint ComputeFlzCompressLen(Transaction tx)
{
byte[] encoded = Rlp.Encode(tx, RlpBehaviors.SkipTypedWrapping).Bytes;

[SkipLocalsInit]
static uint FlzCompressLen(byte[] data)
{
uint n = 0;
uint[] ht = ArrayPool<uint>.Shared.Rent(8192);
try
{
uint u24(uint i) => data[i] | ((uint)data[i + 1] << 8) | ((uint)data[i + 2] << 16);
uint cmp(uint p, uint q, uint e)
{
uint l = 0;
for (e -= q; l < e; l++)
{
if (data[p + (int)l] != data[q + (int)l])
{
e = 0;
}
}
return l;
}
void literals(uint r)
{
n += 0x21 * (r / 0x20);
r %= 0x20;
if (r != 0)
{
n += r + 1;
}
}
void match(uint l)
{
l--;
n += 3 * (l / 262);
if (l % 262 >= 6)
{
n += 3;
}
else
{
n += 2;
}
}
uint hash(uint v) => ((2654435769 * v) >> 19) & 0x1fff;
uint setNextHash(uint ip)
{
ht[hash(u24(ip))] = ip;
return ip + 1;
}
uint a = 0;
uint ipLimit = (uint)data.Length - 13;
if (data.Length < 13)
{
ipLimit = 0;
}
for (uint ip = a + 2; ip < ipLimit;)
{
uint d;
uint r;
for (; ; )
{
uint s = u24(ip);
uint h = hash(s);
r = ht[h];
ht[h] = ip;
d = ip - r;
if (ip >= ipLimit)
{
break;
}
ip++;
if (d <= 0x1fff && s == u24(r))
{
break;
}
}
if (ip >= ipLimit)
{
break;
}
ip--;
if (ip > a)
{
literals(ip - a);
}
uint l = cmp(r + 3, ip + 3, ipLimit + 9);
match(l);
ip = setNextHash(setNextHash(ip + l));
a = ip;
}
literals((uint)data.Length - a);
return n;
}
finally
{
ArrayPool<uint>.Shared.Return(ht);
}
}

return FlzCompressLen(encoded);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public OptimismEthRpcModule(
OptimismTxReceipt[] receipts = receiptFinder.Get(block).Cast<OptimismTxReceipt>().ToArray() ?? new OptimismTxReceipt[block.Transactions.Length];
bool isEip1559Enabled = specProvider.GetSpec(block.Header).IsEip1559Enabled;

L1BlockGasInfo l1BlockGasInfo = new(block, opSpecHelper.IsRegolith(block!.Header));
L1BlockGasInfo l1BlockGasInfo = new(block, opSpecHelper);

OptimismReceiptForRpc[]? result = [.. receipts
.Zip(block.Transactions, (r, t) =>
Expand Down Expand Up @@ -153,7 +153,7 @@ public override async Task<ResultWrapper<Hash256>> eth_sendRawTransaction(byte[]

Block block = foundBlock.Object;

L1BlockGasInfo l1GasInfo = new(block, _opSpecHelper.IsRegolith(block.Header));
L1BlockGasInfo l1GasInfo = new(block, _opSpecHelper);
return ResultWrapper<OptimismReceiptForRpc?>.Success(
new(txHash, (OptimismTxReceipt)receipt, gasInfo.Value, l1GasInfo.GetTxGasInfo(block.Transactions.First(tx => tx.Hash == txHash)), logIndexStart));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ static AuRaParameters.Validator LoadValidator(ChainSpecJson.AuRaValidatorJson va
BedrockBlockNumber = chainSpecJson.Engine.Optimism.BedrockBlockNumber,
CanyonTimestamp = chainSpecJson.Engine.Optimism.CanyonTimestamp,
EcotoneTimestamp = chainSpecJson.Engine.Optimism.EcotoneTimestamp,
FjordTimestamp = chainSpecJson.Engine.Optimism.FjordTimestamp,

L1FeeRecipient = chainSpecJson.Engine.Optimism.L1FeeRecipient,
L1BlockAddress = chainSpecJson.Engine.Optimism.L1BlockAddress,
CanyonBaseFeeChangeDenominator = chainSpecJson.Engine.Optimism.CanyonBaseFeeChangeDenominator,
Expand Down
Loading