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

Add RLP encoding and hashing for Blob txs #5593

Merged
merged 17 commits into from
Jun 2, 2023
Merged
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
141 changes: 113 additions & 28 deletions src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,6 @@ public void Transaction_with_init_code_above_max_value_is_rejected_when_eip3860E

[Timeout(Timeout.MaxTestTime)]
[TestCase(TxType.EIP1559, false, ExpectedResult = true)]
[TestCase(TxType.Blob, false, ExpectedResult = false)]
[TestCase(TxType.EIP1559, true, ExpectedResult = false)]
[TestCase(TxType.Blob, true, ExpectedResult = true)]
public bool MaxFeePerDataGas_should_be_set_for_blob_tx_only(TxType txType, bool isMaxFeePerDataGasSet)
Expand All @@ -304,33 +303,6 @@ public bool MaxFeePerDataGas_should_be_set_for_blob_tx_only(TxType txType, bool
return txValidator.IsWellFormed(tx, Cancun.Instance);
}

[TestCase(0, ExpectedResult = false)]
[TestCase(Eip4844Constants.MinBlobsPerTransaction - 1, ExpectedResult = false)]
[TestCase(Eip4844Constants.MinBlobsPerTransaction, ExpectedResult = true)]
[TestCase(Eip4844Constants.MinBlobsPerTransaction + 1, ExpectedResult = true)]
[TestCase(Eip4844Constants.MaxBlobsPerTransaction - 1, ExpectedResult = true)]
[TestCase(Eip4844Constants.MaxBlobsPerTransaction, ExpectedResult = true)]
[TestCase(Eip4844Constants.MaxBlobsPerTransaction + 1, ExpectedResult = false)]
public bool Blobs_count_should_be_within_constraints(int blobsCount)
{
byte[] sigData = new byte[65];
sigData[31] = 1; // correct r
sigData[63] = 1; // correct s
sigData[64] = 27; // correct v
Signature signature = new(sigData);
Transaction tx = Build.A.Transaction
.WithType(TxType.Blob)
.WithTimestamp(ulong.MaxValue)
.WithMaxFeePerGas(1)
.WithMaxFeePerDataGas(1)
.WithBlobVersionedHashes(blobsCount)
.WithChainId(TestBlockchainIds.ChainId)
.WithSignature(signature).TestObject;

TxValidator txValidator = new(TestBlockchainIds.ChainId);
return txValidator.IsWellFormed(tx, Cancun.Instance);
}


[TestCaseSource(nameof(BlobVersionedHashInvalidTestCases))]
[TestCaseSource(nameof(BlobVersionedHashValidTestCases))]
Expand All @@ -354,6 +326,13 @@ public bool BlobVersionedHash_should_be_correct(byte[] hash)
return txValidator.IsWellFormed(tx, Cancun.Instance);
}

[TestCaseSource(nameof(ShardBlobTxIncorrectTransactions))]
public bool ShardBlobTransaction_fields_should_be_verified(Transaction tx)
{
TxValidator txValidator = new(TestBlockchainIds.ChainId);
return txValidator.IsWellFormed(tx, Cancun.Instance);
}

private static byte[] MakeArray(int count, params byte[] elements) =>
elements.Take(Math.Min(count, elements.Length))
.Concat(new byte[Math.Max(0, count - elements.Length)])
Expand Down Expand Up @@ -409,4 +388,110 @@ private static IEnumerable<TestCaseData> BlobVersionedHashValidTestCases
};
}
}

private static IEnumerable<TestCaseData> ShardBlobTxIncorrectTransactions
{
get
{
KzgPolynomialCommitments.InitializeAsync().Wait();
static TransactionBuilder<Transaction> MakeTestObject(int blobCount = 1) => Build.A.Transaction
.WithChainId(TestBlockchainIds.ChainId)
.WithTimestamp(ulong.MaxValue)
.WithMaxFeePerGas(1)
.WithMaxFeePerDataGas(1)
.WithShardBlobTxTypeAndFields(blobCount);

yield return new TestCaseData(MakeTestObject().SignedAndResolved().TestObject)
{
TestName = "A correct shard blob tx",
ExpectedResult = true
};

yield return new TestCaseData(MakeTestObject(0)
.SignedAndResolved().TestObject)
{
TestName = "BlobVersionedHashes are empty",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MinBlobsPerTransaction - 1)
.SignedAndResolved().TestObject)
{
TestName = "Not enough BlobVersionedHashes",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MinBlobsPerTransaction)
.SignedAndResolved().TestObject)
{
TestName = "Bare minimum BlobVersionedHashes",
ExpectedResult = true
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MinBlobsPerTransaction + 1)
.SignedAndResolved().TestObject)
{
TestName = "More than minimum BlobVersionedHashes",
ExpectedResult = true
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction - 1)
.SignedAndResolved().TestObject)
{
TestName = "Less than maximum BlobVersionedHashes",
ExpectedResult = true
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction)
.SignedAndResolved().TestObject)
{
TestName = "Maximum BlobVersionedHashes",
ExpectedResult = true
};
yield return new TestCaseData(MakeTestObject(Eip4844Constants.MaxBlobsPerTransaction + 1)
.SignedAndResolved().TestObject)
{
TestName = "Too many BlobVersionedHashes",
ExpectedResult = false
};

yield return new TestCaseData(MakeTestObject()
.WithBlobVersionedHashes(new byte[][] { MakeArray(31, KzgPolynomialCommitments.KzgBlobHashVersionV1) })
.SignedAndResolved().TestObject)
{
TestName = "BlobVersionedHashes are of a wrong length",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject()
.With(tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Blobs = Array.Empty<byte[]>())
.SignedAndResolved().TestObject)
{
TestName = "Blobs count does not match hashes count",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject()
.With(tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Commitments = Array.Empty<byte[]>())
.SignedAndResolved().TestObject)
{
TestName = "Commitments count does not match hashes count",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject()
.With(tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Proofs = Array.Empty<byte[]>())
.SignedAndResolved().TestObject)
{
TestName = "Proofs count does not match hashes count",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject()
.With(tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Commitments[0][1] ^= 0xFF)
.SignedAndResolved().TestObject)
{
TestName = "A commitment does not math hash",
ExpectedResult = false
};
yield return new TestCaseData(MakeTestObject()
.With(tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Proofs[0][1] ^= 0xFF)
.SignedAndResolved().TestObject)
{
TestName = "Proofs are not valid",
ExpectedResult = false
};
}
}
}
17 changes: 2 additions & 15 deletions src/Nethermind/Nethermind.Cli/Console/ColorfulCliConsole.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
// Copyright (c) 2022 Demerzel Solutions Limited
// This file is part of the Nethermind library.
//
// The Nethermind library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Nethermind library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Nethermind. If not, see <http://www.gnu.org/licenses/>.
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Drawing;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Int256;

namespace Nethermind.Consensus.Transactions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Int256;
using Nethermind.Logging;
using Nethermind.TxPool;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System.Collections.Generic;
using Nethermind.Core;
using Nethermind.Int256;

namespace Nethermind.Consensus.Transactions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Linq;
using Nethermind.Core;
using Nethermind.Int256;

namespace Nethermind.Consensus.Transactions
{
Expand Down
45 changes: 26 additions & 19 deletions src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,12 @@ private bool ValidateSignature(Transaction tx, IReleaseSpec spec)

private static bool Validate4844Fields(Transaction transaction)
{
// Execution-payload version part
if (transaction.Type != TxType.Blob)
// Execution-payload version verification
if (!transaction.SupportsBlobs)
{
return transaction.MaxFeePerDataGas is null &&
transaction.BlobVersionedHashes is null &&
transaction.BlobKzgs is null &&
transaction.Blobs is null &&
transaction.BlobProofs is null;
transaction is not { NetworkWrapper: ShardBlobNetworkWrapper };
}

if (transaction.MaxFeePerDataGas is null ||
Expand All @@ -126,7 +124,9 @@ transaction.BlobVersionedHashes is null ||
return false;
}

for (int i = 0; i < transaction.BlobVersionedHashes!.Length; i++)
int blobCount = transaction.BlobVersionedHashes.Length;

for (int i = 0; i < blobCount; i++)
{
if (transaction.BlobVersionedHashes[i] is null ||
transaction.BlobVersionedHashes![i].Length !=
Expand All @@ -137,32 +137,39 @@ transaction.BlobVersionedHashes is null ||
}
}

// And mempool version part if presents
if (transaction.BlobVersionedHashes!.Length > 0 && (transaction.Blobs is not null ||
transaction.BlobKzgs is not null ||
transaction.BlobProofs is not null))
// Mempool version verification if presents
flcl42 marked this conversation as resolved.
Show resolved Hide resolved
if (transaction.NetworkWrapper is ShardBlobNetworkWrapper wrapper)
{
if (transaction.BlobKzgs is null)
if (wrapper.Blobs.Length != blobCount ||
wrapper.Commitments.Length != blobCount ||
wrapper.Proofs.Length != blobCount)
{
return false;
}

for (int i = 0; i < blobCount; i++)
{
if (wrapper.Blobs[i].Length != Ckzg.Ckzg.BytesPerBlob ||
wrapper.Commitments[i].Length != Ckzg.Ckzg.BytesPerCommitment ||
wrapper.Proofs[i].Length != Ckzg.Ckzg.BytesPerProof)
{
return false;
}
}

Span<byte> hash = stackalloc byte[32];
Span<byte> commitements = transaction.BlobKzgs;
for (int i = 0, n = 0;
i < transaction.BlobVersionedHashes!.Length;
i++, n += Ckzg.Ckzg.BytesPerCommitment)
for (int i = 0; i < blobCount; i++)
{
if (!KzgPolynomialCommitments.TryComputeCommitmentHashV1(
commitements[n..(n + Ckzg.Ckzg.BytesPerCommitment)], hash) ||
!hash.SequenceEqual(transaction.BlobVersionedHashes![i]))
wrapper.Commitments[i].AsSpan(), hash) ||
!hash.SequenceEqual(transaction.BlobVersionedHashes[i]))
{
return false;
}
}

return KzgPolynomialCommitments.AreProofsValid(transaction.Blobs!,
transaction.BlobKzgs!, transaction.BlobProofs!);
return KzgPolynomialCommitments.AreProofsValid(wrapper.Blobs,
wrapper.Commitments, wrapper.Proofs);
}

return true;
Expand Down
30 changes: 30 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/AssertionsSetup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using NUnit.Framework;
using NUnit.Framework.Internal;
using FluentAssertions;
using Nethermind.Core.Extensions;

namespace Nethermind;

/// <summary>
/// Global settings for the fluent assertions, works for the current assembly only.
/// </summary>
[SetUpFixture]
public class AssertionsSetup
{
[OneTimeSetUp]
public void RunBeforeAnyTests()
{
AssertionOptions.AssertEquivalencyUsing(options =>
flcl42 marked this conversation as resolved.
Show resolved Hide resolved
{
options
.Using<Memory<byte>>(context =>
context.Subject.AsArray().Should().BeEquivalentTo(context.Expectation.AsArray()))
.WhenTypeIs<Memory<byte>>();
return options;
});
}
}
20 changes: 20 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Builders/AccessListBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

using System.Linq;
using Nethermind.Core.Eip2930;

namespace Nethermind.Core.Test.Builders;
public class TestAccessListBuilder : BuilderBase<AccessList>
{
public TestAccessListBuilder()
{
AccessListBuilder accessListBuilder = new();
foreach (Address address in TestItem.Addresses.Take(5))
{
accessListBuilder.AddAddress(address);
}

TestObjectInternal = accessListBuilder.ToAccessList();
}
}
10 changes: 10 additions & 0 deletions src/Nethermind/Nethermind.Core.Test/Builders/Build.AccessList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
// SPDX-License-Identifier: LGPL-3.0-only

namespace Nethermind.Core.Test.Builders
{
public partial class Build
{
public TestAccessListBuilder AccessList => new();
}
}
Loading