Skip to content

Commit

Permalink
Update config, gas calculations, receipt fields according to OP's Fjo…
Browse files Browse the repository at this point in the history
…rd (#7129)

Co-authored-by: Nikita Mescheryakov <root@nikitam.io>
  • Loading branch information
flcl42 and deffrian authored Jun 7, 2024
1 parent 3df20d2 commit fdf8372
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 15 deletions.
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

0 comments on commit fdf8372

Please sign in to comment.