diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs index 1418fe28401..a2f4830cb7a 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs @@ -87,7 +87,7 @@ public virtual TxReceipt[] ProcessTransactions(Block block, ProcessingOptions pr return receiptsTracer.TxReceipts.ToArray(); } - private TxAction ProcessTransaction( + protected TxAction ProcessTransaction( Block block, Transaction currentTx, int index, diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs index 31c97c9d263..34229b91ef1 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockValidationTransactionsExecutor.cs @@ -28,7 +28,7 @@ public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionCont transactionProcessor.SetBlockExecutionContext(in blockExecutionContext); } - public TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, CancellationToken token) + public virtual TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, CancellationToken token) { Metrics.ResetBlockStats(); diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs index ce6d7be6f11..016bb940a69 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockToProduce.cs @@ -9,6 +9,7 @@ //TODO: Redo clique block producer [assembly: InternalsVisibleTo("Nethermind.Consensus.Clique")] +[assembly: InternalsVisibleTo("Nethermind.Xdc")] [assembly: InternalsVisibleTo("Nethermind.Blockchain.Test")] namespace Nethermind.Consensus.Producers diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs index cc8e1349d68..178027c5d29 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs @@ -243,7 +243,7 @@ public void Dispose() ArrayPoolListCore.Dispose(_arrayPool, ref _array, ref _count, ref _capacity, ref _disposed); #if DEBUG - GC.SuppressFinalize(this); + GC.SuppressFinalize(this); #endif } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index b2c3a7f5fec..69e70ccebe2 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -22,7 +22,7 @@ namespace Nethermind.Evm.TransactionProcessing { - public sealed class TransactionProcessor( + public class TransactionProcessor( ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, ISpecProvider? specProvider, IWorldState? worldState, @@ -963,6 +963,7 @@ private TransactionResult(ErrorType error = ErrorType.None, EvmExceptionType evm ErrorType.SenderNotSpecified => "sender not specified", ErrorType.TransactionSizeOverMaxInitCodeSize => "EIP-3860 - transaction size over max init code size", ErrorType.WrongTransactionNonce => "wrong transaction nonce", + ErrorType.ContainsBlacklistedAddress => "sender or receiver are blacklisted addresses", _ => "" }; public static implicit operator TransactionResult(ErrorType error) => new(error); @@ -993,6 +994,7 @@ public static TransactionResult EvmException(EvmExceptionType evmExceptionType, public static readonly TransactionResult SenderNotSpecified = ErrorType.SenderNotSpecified; public static readonly TransactionResult TransactionSizeOverMaxInitCodeSize = ErrorType.TransactionSizeOverMaxInitCodeSize; public static readonly TransactionResult WrongTransactionNonce = ErrorType.WrongTransactionNonce; + public static readonly TransactionResult ContainsBlacklistedAddress = ErrorType.ContainsBlacklistedAddress; public enum ErrorType { @@ -1008,6 +1010,7 @@ public enum ErrorType SenderNotSpecified, TransactionSizeOverMaxInitCodeSize, WrongTransactionNonce, + ContainsBlacklistedAddress } } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs index 33f5e224e9d..31bff504585 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -11,11 +11,13 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Db; +using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Facade.Find; using Nethermind.Int256; @@ -219,6 +221,22 @@ private IXdcReleaseSpec WrapReleaseSpec(IReleaseSpec spec) xdcSpec.MinimumSigningTx = 1; xdcSpec.GasLimitBoundDivisor = 1024; + xdcSpec.BlackListedAddresses = + [ + new Address("0x00000000000000000000000000000000b1Ac701"), + new Address("0x00000000000000000000000000000000b1Ac702"), + new Address("0x00000000000000000000000000000000b1Ac703"), + new Address("0x00000000000000000000000000000000b1Ac704"), + new Address("0x00000000000000000000000000000000b1Ac705"), + new Address("0x00000000000000000000000000000000b1Ac706"), + new Address("0x00000000000000000000000000000000b1Ac707"), + ]; + xdcSpec.MergeSignRange = 15; + + xdcSpec.RandomizeSMCBinary = new Address("0x0000000000000000000000000000000000000089"); + xdcSpec.BlockSignersAddress = new Address("0x0000000000000000000000000000000000000090"); + xdcSpec.TIP2019Block = 0; + V2ConfigParams[] v2ConfigParams = [ new V2ConfigParams { SwitchRound = 0, @@ -310,9 +328,30 @@ public Block Build() state.CreateAccount(TestItem.AddressA, testConfiguration.AccountInitialValue); state.CreateAccount(TestItem.AddressB, testConfiguration.AccountInitialValue); state.CreateAccount(TestItem.AddressC, testConfiguration.AccountInitialValue); + state.CreateAccount(TestItem.AddressD, testConfiguration.AccountInitialValue); + state.CreateAccount(TestItem.AddressF, testConfiguration.AccountInitialValue); + + + var genesisSpec = specProvider.GenesisSpec as IXdcReleaseSpec; + + state.CreateAccount(genesisSpec!.BlockSignersAddress, 100_000); + state.CreateAccount(genesisSpec!.RandomizeSMCBinary, 100_000); + + var dummyCode = Prepare.EvmCode + .STOP() + .Done; + var dummyCodeHashcode = Keccak.Compute(dummyCode); + + state.InsertCode(genesisSpec.BlockSignersAddress, dummyCodeHashcode, dummyCode, genesisSpec, true); + state.InsertCode(genesisSpec.RandomizeSMCBinary, dummyCodeHashcode, dummyCode, genesisSpec, true); IXdcReleaseSpec? finalSpec = (IXdcReleaseSpec)specProvider.GetFinalSpec(); + foreach (var nodeAddress in finalSpec.GenesisMasterNodes) + { + state.CreateAccount(nodeAddress, testConfiguration.AccountInitialValue); + } + XdcBlockHeaderBuilder xdcBlockHeaderBuilder = new(); var genesisBlock = new Block(xdcBlockHeaderBuilder @@ -367,6 +406,7 @@ public async Task AddBlocks(int count, bool withTransaction = false) } } + public override async Task AddBlock(params Transaction[] transactions) { var b = await AddBlockWithoutCommitQc(transactions); diff --git a/src/Nethermind/Nethermind.Xdc.Test/SpecialTransactionsTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SpecialTransactionsTests.cs new file mode 100644 index 00000000000..5a64b0380c3 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/SpecialTransactionsTests.cs @@ -0,0 +1,470 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using FluentAssertions; +using FluentAssertions.Extensions; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Headers; +using Nethermind.Consensus; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Blockchain; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.TxPool; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Test.Helpers; +using NSubstitute; +using NSubstitute.ExceptionExtensions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Nethermind.Xdc.Test; + +internal class SpecialTransactionsTests +{ + private bool IsTimeForOnchainSignature(IXdcReleaseSpec spec, long blockNumber) + { + return blockNumber % spec.MergeSignRange == 0; + } + + private Task ProposeBatchTransferTxFrom(PrivateKey source, PrivateKey destination, UInt256 amount, int count, XdcTestBlockchain chain) + { + return Task.Run(() => + { + (PrivateKey, PrivateKey) swap(PrivateKey a, PrivateKey b) => (b, a); + + for (int i = 0; i < count; i++) + { + (source, destination) = swap(source, destination); + CreateTransferTxFrom(source, destination, amount, chain); + } + }); + } + + private Transaction CreateTransferTxFrom(PrivateKey source, PrivateKey destination, UInt256 amount, XdcTestBlockchain chain) + { + Transaction tx = Build.A.Transaction + .WithSenderAddress(source.Address) + .WithTo(destination.Address) + .WithValue(amount) + .WithType(TxType.Legacy) + .TestObject; + + var signer = new Signer(chain.SpecProvider.ChainId, source, NullLogManager.Instance); + signer.Sign(tx); + + tx.Hash = tx.CalculateHash(); + + var result = chain.TxPool.SubmitTx(tx, TxHandlingOptions.None); + + return tx; + } + + private PrivateKey[] FilledAccounts(XdcTestBlockchain chain) + { + var genesisSpec = chain.SpecProvider.GenesisSpec as XdcReleaseSpec; + var pks = chain.MasterNodeCandidates + .Where(k => genesisSpec!.GenesisMasterNodes.Contains(k.Address)); + return pks.ToArray(); + } + + [Test] + public async Task Special_Tx_Is_Dispatched_On_MergeSignRange_Block() + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = false; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + do + { + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + await Task.Delay(blockChain.SpecProvider.GetXdcSpec(head!).MinePeriod.Seconds()); // to avoid tight loop + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + while (!IsTimeForOnchainSignature(blockChain.SpecProvider.GetXdcSpec(head), head.Number - 1)); + + + + Assert.That(blockChain.BlockTree.Head.Number, Is.EqualTo(mergeSignBlockRange + 1)); + + await Task.Delay(((XdcReleaseSpec)blockChain.SpecProvider.GetFinalSpec()).MinePeriod.Seconds()); // to avoid tight loop + + Transaction[] pendingTxs = blockChain.TxPool.GetPendingTransactions(); + + var specialTxs = pendingTxs.Where(r => r.To == blockChain.SpecProvider.GetXdcSpec(head).BlockSignersAddress + || r.To == blockChain.SpecProvider.GetXdcSpec(head).RandomizeSMCBinary); + + Assert.That(specialTxs, Is.Not.Empty); + + var specialTx = specialTxs.First(); + + var blockTarget = (long)(new UInt256(specialTx.Data.Span.Slice(4, 32), true)); + + Assert.That(blockTarget, Is.EqualTo(mergeSignBlockRange)); + } + + [Test] + public async Task Special_Tx_Is_Not_Dispatched_Outside_MergeSignRange_Block() + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = false; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + do + { + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + await Task.Delay(blockChain.SpecProvider.GetXdcSpec(head!).MinePeriod.Seconds()); // to avoid tight loop + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + while (!IsTimeForOnchainSignature(blockChain.SpecProvider.GetXdcSpec(head), head.Number + 1)); + + // await blockChain.StopHotStuffModule(); + + await Task.Delay(((XdcReleaseSpec)blockChain.SpecProvider.GetFinalSpec()).MinePeriod.Seconds()); // to avoid tight loop + + var receipts = blockChain.TxPool.GetPendingTransactions(); + + receipts.Any(r => r.To == blockChain.SpecProvider.GetXdcSpec(head).BlockSignersAddress + || r.To == blockChain.SpecProvider.GetXdcSpec(head).RandomizeSMCBinary).Should().BeFalse(); + } + + [Test] + public async Task Special_Tx_Is_Executed_Before_Normal_Txs() + { + var blockChain = await XdcTestBlockchain.Create(1, true); + + var mergeSignBlockRange = 5; + + blockChain.ChangeReleaseSpec((spec) => + { + spec.MergeSignRange = mergeSignBlockRange; + spec.IsEip1559Enabled = false; + }); + + blockChain.StartHotStuffModule(); + + XdcBlockHeader? head = blockChain.BlockTree.Head!.Header as XdcBlockHeader; + var spec = blockChain.SpecProvider.GetXdcSpec(head!); + + var random = new Random(); + + var accounts = FilledAccounts(blockChain); + + for (int i = 1; i < spec.MergeSignRange + 2; i++) + { + if (head!.Number == mergeSignBlockRange + 1) + { + var source = accounts.ElementAt(random.Next() % accounts.Length); + var dest = accounts.Except([source]).ElementAt(random.Next() % (accounts.Length - 1)); + await ProposeBatchTransferTxFrom(source, dest, 1, 2, blockChain); + } + + await blockChain.TriggerAndSimulateBlockProposalAndVoting(); + await Task.Delay(blockChain.SpecProvider.GetXdcSpec(head!).MinePeriod.Seconds()); // to avoid tight loop + head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header; + } + + var block = (XdcBlockHeader)blockChain.BlockTree.Head.Header; + spec = blockChain.SpecProvider.GetXdcSpec(block!); + + var receipts = blockChain.ReceiptStorage.Get(block.Hash!); + + Assert.That(receipts, Is.Not.Empty); + Assert.That(receipts.Length, Is.GreaterThan(1)); + + bool onlyEncounteredSpecialTx = true; + foreach (var transaction in receipts) + { + if (transaction.Recipient == spec.BlockSignersAddress || transaction.Recipient == spec.RandomizeSMCBinary) + { + if (!onlyEncounteredSpecialTx) + { + // we encountered a normal transaction before so special txs are not lumped at the start + Assert.Fail(); + } + } + else + { + onlyEncounteredSpecialTx = false; + } + } + + Assert.Pass(); + } + + [TestCase(false)] + [TestCase(true)] + public async Task Tx_With_With_BlackListed_Sender_Fails_Validation(bool blackListingActivated) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.BlackListedAddresses = [blockChain.Signer.Address]; + spec.IsEip1559Enabled = false; + spec.BlackListHFNumber = blackListingActivated ? 0 : long.MaxValue; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + var txSign = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignersAddress, blockChain.Signer.Address); + await blockChain.Signer.Sign(txSign); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(txSign, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + if (blackListingActivated) + { + result.Value.Error.Should().Be(TransactionResult.ErrorType.ContainsBlacklistedAddress); + } + else + { + result.Value.Error.Should().NotBe(TransactionResult.ErrorType.ContainsBlacklistedAddress); + } + } + + + [TestCase(false)] + [TestCase(true)] + public async Task Tx_With_With_BlackListed_Receiver_Fails_Validation(bool blackListingActivated) + { + + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.BlackListedAddresses = [TestItem.AddressA]; + spec.IsEip1559Enabled = false; + spec.BlackListHFNumber = blackListingActivated ? 0 : long.MaxValue; + }); + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + Transaction tx = Build.A.Transaction.WithSenderAddress(blockChain.Signer.Address).WithTo(TestItem.AddressA).TestObject; + await blockChain.Signer.Sign(tx); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + if (blackListingActivated) + { + result.Value.Error.Should().Be(TransactionResult.ErrorType.ContainsBlacklistedAddress); + } + else + { + result.Value.Error.Should().NotBe(TransactionResult.ErrorType.ContainsBlacklistedAddress); + } + } + + [TestCase(true)] + [TestCase(false)] + public async Task Malformed_WrongLenght_SpecialTx_Fails_Validation(bool isSpecialTx) + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + Transaction? tx = null; + + if (isSpecialTx) + { + tx = SignTransactionManager.CreateTxSign((UInt256)head.Number, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignersAddress, blockChain.Signer.Address); + } + else + { + tx = Build.A.Transaction.WithSenderAddress(blockChain.Signer.Address).WithTo(TestItem.AddressA).TestObject; + } + + // damage the data field in the tx + tx.Data = Enumerable.Range(0, 48).Select(i => (byte)i).ToArray(); + + await blockChain.Signer.Sign(tx); + + TransactionResult? result = null; + + try + { + result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + } + catch + { + result = TransactionResult.Ok; + } + + if (isSpecialTx) + { + result.Value.Error.Should().Be(TransactionResult.ErrorType.MalformedTransaction); + } + else + { + result.Value.Error.Should().NotBe(TransactionResult.ErrorType.MalformedTransaction); + } + } + + public async Task Malformed_WrongBlockNumber_BlockLessThanCurrent_SpecialTx_Fails_Validation() + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + var blockNumber = head.Number - 1; + Transaction? tx = SignTransactionManager.CreateTxSign((UInt256)blockNumber, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignersAddress, blockChain.Signer.Address); + + await blockChain.Signer.Sign(tx); + + TransactionResult? result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + + result.Value.Error.Should().NotBe(TransactionResult.ErrorType.MalformedTransaction); + } + + public async Task Malformed_WrongBlockNumber_BlockEqualToCurrent_SpecialTx_Fails_Validation() + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + var blockNumber = head.Number; + Transaction? tx = SignTransactionManager.CreateTxSign((UInt256)blockNumber, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignersAddress, blockChain.Signer.Address); + + await blockChain.Signer.Sign(tx); + + TransactionResult? result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + + result.Value.Error.Should().Be(TransactionResult.ErrorType.MalformedTransaction); + } + + public async Task Malformed_WrongBlockNumber_BlockBiggerThanCurrent_SpecialTx_Fails_Validation() + { + var blockChain = await XdcTestBlockchain.Create(5, false); + blockChain.ChangeReleaseSpec((spec) => + { + spec.IsEip1559Enabled = false; + }); + + var moqVm = new VirtualMachine(new BlockhashProvider(new BlockhashCache(blockChain.Container.Resolve(), NullLogManager.Instance), blockChain.WorldStateManager.GlobalWorldState, NullLogManager.Instance), blockChain.SpecProvider, NullLogManager.Instance); + + var transactionProcessor = new XdcTransactionProcessor(BlobBaseFeeCalculator.Instance, blockChain.SpecProvider, blockChain.WorldStateManager.GlobalWorldState, moqVm, NSubstitute.Substitute.For(), NullLogManager.Instance); + + + XdcBlockHeader head = (XdcBlockHeader)blockChain.BlockTree.Head!.Header!; + XdcReleaseSpec spec = (XdcReleaseSpec)blockChain.SpecProvider.GetXdcSpec(head); + + + moqVm.SetBlockExecutionContext(new BlockExecutionContext(head, spec)); + + + var blockNumber = head.Number + 1; + Transaction? tx = SignTransactionManager.CreateTxSign((UInt256)blockNumber, head.Hash!, blockChain.TxPool.GetLatestPendingNonce(TestItem.AddressA), spec.BlockSignersAddress, blockChain.Signer.Address); + + await blockChain.Signer.Sign(tx); + + TransactionResult? result = transactionProcessor.Execute(tx, NullTxTracer.Instance); + + result.Value.Error.Should().Be(TransactionResult.ErrorType.MalformedTransaction); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs b/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs new file mode 100644 index 00000000000..ab1da9ce73b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/ISignTransactionManager.cs @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Xdc.Spec; +using System.Threading.Tasks; + +namespace Nethermind.Xdc; + +internal interface ISignTransactionManager +{ + Task CreateTransactionSign(XdcBlockHeader header, IXdcReleaseSpec spec); +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs b/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs new file mode 100644 index 00000000000..e80d87ca9ef --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/SignTransactionManager.cs @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using DotNetty.Common.Utilities; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.DataProtection.KeyManagement; +using Nethermind.Consensus; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Crypto; +using Nethermind.Db; +using Nethermind.Evm.State; +using Nethermind.Int256; +using Nethermind.TxPool; +using Nethermind.Wallet; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using static Microsoft.FSharp.Core.ByRefKinds; +using static System.Net.Mime.MediaTypeNames; + +namespace Nethermind.Xdc; + +internal class SignTransactionManager(IDb stateDb, ISigner signer, ITxPool txPool) : ISignTransactionManager +{ + const string HexSignMethod = "e341eaa4"; + const string HexSetSecret = "34d38600"; + const string HexSetOpening = "e11f5ba2"; + + public async Task CreateTransactionSign(XdcBlockHeader header, IXdcReleaseSpec spec) + { + UInt256 nonce = txPool.GetLatestPendingNonce(signer.Address); + Transaction transaction = CreateTxSign((UInt256)header.Number, header.Hash, nonce, spec.BlockSignersAddress, signer.Address); + + await signer.Sign(transaction); + + // add local somehow to tx pool + bool added = txPool.SubmitTx(transaction, TxHandlingOptions.PersistentBroadcast); + if (!added) + { + throw new Exception("Failed to add signed transaction to the pool."); + } + + long blockNumber = header.Number; + long checkNumber = blockNumber % spec.EpochLength; + + PrivateKeyGenerator privateKeyGenerator = new PrivateKeyGenerator(); + PrivateKey randomPrivate = privateKeyGenerator.Generate(); + + byte[] randomKey = Encoding.UTF8.GetBytes("randomizeKey"); + + var exists = stateDb.KeyExists(randomKey); + + if (exists) + { + if (checkNumber > 0 && spec.EpochBlockOpening <= checkNumber && spec.EpochBlockRandomize >= checkNumber) + { + var randomizeKeyValue = stateDb.Get(randomKey); + Transaction tx = CreateTxOpeningRandomize(nonce + 1, spec.RandomizeSMCBinary, randomizeKeyValue, signer.Address); + await signer.Sign(tx); + + // add local somehow to tx pool + bool addedOpening = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); + + stateDb.Remove(randomKey); + } + } + else + { + var randomizeKeyValue = RandStringByte(32); + if (checkNumber > 0 && spec.EpochBlockOpening <= checkNumber && spec.EpochBlockRandomize > checkNumber) + { + Transaction tx = BuildTxSecretRandomize(nonce + 1, spec.RandomizeSMCBinary, (ulong)spec.EpochLength, randomizeKeyValue, signer.Address); + await signer.Sign(tx); + // add local somehow to tx pool + bool addedOpening = txPool.SubmitTx(tx, TxHandlingOptions.PersistentBroadcast); + + stateDb.PutSpan(randomKey, randomizeKeyValue); + } + } + } + + internal static Transaction CreateTxSign(UInt256 number, Hash256 hash, UInt256 nonce, Address blockSignersAddress, Address sender) + { + var functionSelector = Bytes.FromHexString(HexSignMethod); // hexSetSecret like "0x..." (method id +) + byte[] inputData = [.. functionSelector, .. number.PaddedBytes(32), .. hash.Bytes.PadLeft(32)]; + + var transaction = new Transaction(); + transaction.Nonce = nonce; + transaction.To = blockSignersAddress; + transaction.Value = 0; + transaction.GasLimit = 200_000; + transaction.GasPrice = 0; + transaction.Data = inputData; + transaction.SenderAddress = sender; + + transaction.Type = TxType.Legacy; + + transaction.Hash = transaction.CalculateHash(); + + return transaction; + } + + internal static Transaction CreateTxOpeningRandomize(UInt256 nonce, Address randomizeSMCBinary, byte[] randomizeKey, Address sender) + { + var functionSelector = Bytes.FromHexString(HexSetOpening); // hexSetSecret like "0x..." (method id +) + byte[] inputData = [.. functionSelector, .. randomizeKey]; + + var transaction = new Transaction(); + transaction.Nonce = nonce; + transaction.To = randomizeSMCBinary; + transaction.Value = 0; + transaction.GasLimit = 200_000; + transaction.GasPrice = 0; + transaction.Data = inputData; + transaction.SenderAddress = sender; + + transaction.Type = TxType.Legacy; + + transaction.Hash = transaction.CalculateHash(); + + return transaction; + } + + internal static Transaction BuildTxSecretRandomize(UInt256 nonce, Address randomizeSMCBinary, ulong epochNumber, byte[] randomizeKey, Address sender) + { + var functionSelector = Bytes.FromHexString(HexSetSecret); // hexSetSecret like "0x..." (method id +) + var secretNumb = RandomNumberGenerator.GetInt32((int)epochNumber); + + var secrets = new long[] { secretNumb }; + const int sizeOfArray = 32; + + var arrSizeOfSecrets = (UInt256)sizeOfArray; + var arrLengthOfSecrets = (UInt256)secrets.Length; + + List input = [.. functionSelector, .. arrSizeOfSecrets.PaddedBytes(32), .. arrLengthOfSecrets.PaddedBytes(32)]; + + foreach (var secret in secrets) + { + var enc = Encrypt(randomizeKey, secret.ToString()); // base64-url string + var encBytes = Encoding.UTF8.GetBytes(enc); + var padded = encBytes.PadLeft(sizeOfArray); + input.AddRange(padded); + } + + var inputData = input.ToArray(); + // Build TransactionInput (no from set here). Caller may sign/create raw tx with nonce/gas/value. + var transaction = new Transaction(); + transaction.Nonce = nonce; + transaction.To = randomizeSMCBinary; + transaction.Value = 0; + transaction.GasLimit = 200_000; + transaction.GasPrice = 0; + transaction.Data = inputData; + transaction.SenderAddress = sender; + + transaction.Type = TxType.Legacy; + + transaction.Hash = transaction.CalculateHash(); + + return transaction; + } + + private static string Encrypt(byte[] randomizeKey, string text) + { + using var aes = Aes.Create(); + aes.Key = randomizeKey; + aes.Mode = CipherMode.CFB; + aes.Padding = PaddingMode.None; + aes.BlockSize = 128; + + var iv = new byte[aes.BlockSize / 8]; + RandomNumberGenerator.Fill(iv); + + var plaintext = Encoding.UTF8.GetBytes(text); + using var ms = new MemoryStream(); + // prepend IV + ms.Write(iv, 0, iv.Length); + using (var encryptor = aes.CreateEncryptor(aes.Key, iv)) + using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write)) + { + cs.Write(plaintext, 0, plaintext.Length); + cs.FlushFinalBlock(); + } + + var cipherBytes = ms.ToArray(); + return Convert.ToBase64String(cipherBytes); + } + + internal static byte[] RandStringByte(int n) + { + const string letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"; + var result = new byte[n]; + for (int i = 0; i < n; i++) + { + int idx = RandomNumberGenerator.GetInt32(letterBytes.Length); + result[i] = (byte)letterBytes[idx]; + } + return result; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs index bf0b7453dff..5873a574ff5 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs @@ -31,6 +31,14 @@ public class XdcReleaseSpec : ReleaseSpec, IXdcReleaseSpec public List V2Configs { get; set; } = new List(); public Address[] GenesisMasterNodes { get; set; } + public Address BlockSignersAddress { get; set; } + public Address RandomizeSMCBinary { get; set; } + public long BlackListHFNumber { get; set; } + public long EpochBlockOpening { get; set; } + public long EpochBlockRandomize { get; set; } + public long MergeSignRange { get; set; } + public long TIP2019Block { get; set; } + public Address[] BlackListedAddresses { get; set; } public void ApplyV2Config(ulong round) { @@ -97,5 +105,15 @@ public interface IXdcReleaseSpec : IReleaseSpec public int MinimumSigningTx { get; set; } // Signing txs that a node needs to produce to get out of penalty, after `LimitPenaltyEpoch` public List V2Configs { get; set; } Address[] GenesisMasterNodes { get; set; } + Address BlockSignersAddress { get; set; } + Address RandomizeSMCBinary { get; set; } + long BlackListHFNumber { get; set; } + long EpochBlockOpening { get; set; } + long EpochBlockRandomize { get; set; } + long MergeSignRange { get; set; } + long TIP2019Block { get; set; } + + Address[] BlackListedAddresses { get; set; } + public void ApplyV2Config(ulong round); } diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockBuildingTransactionExecutor.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockBuildingTransactionExecutor.cs new file mode 100644 index 00000000000..cee606dc4c4 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockBuildingTransactionExecutor.cs @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Tracing; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Specs; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Logging; +using Nethermind.State.Proofs; +using Nethermind.TxPool.Comparison; +using System.Collections.Generic; +using System.Threading; +using Nethermind.TxPool; +using static Nethermind.Consensus.Processing.BlockProcessor; + +namespace Nethermind.Xdc; + +internal class XdcBlockBuildingTransactionExecutor( + ITransactionProcessorAdapter transactionProcessor, + IWorldState stateProvider, + IBlockProductionTransactionPicker txPicker, + ISpecProvider specProvider, + ILogManager logManager) : BlockProcessor.BlockProductionTransactionsExecutor(transactionProcessor, stateProvider, txPicker, logManager) +{ + public override TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, CancellationToken token) + { + // We start with high number as don't want to resize too much + const int defaultTxCount = 512; + + var spec = specProvider.GetXdcSpec(block.Header as XdcBlockHeader); + + BlockToProduce? blockToProduce = block as BlockToProduce; + + // Don't use blockToProduce.Transactions.Count() as that would fully enumerate which is expensive + int txCount = blockToProduce is not null ? defaultTxCount : block.Transactions.Length; + IEnumerable transactions = blockToProduce?.Transactions ?? block.Transactions; + + ArrayPoolListRef includedTx = new(txCount); + + HashSet consideredTx = new(ByHashTxComparer.Instance); + int i = 0; + + HashSet delayedTx = new(ByHashTxComparer.Instance); + try + { + + foreach (Transaction currentTx in transactions) + { + // Check if we have gone over time or the payload has been requested + if (token.IsCancellationRequested) break; + + if (!currentTx.IsSpecialTransaction(spec)) + { + delayedTx.Add(currentTx); + continue; + } + + if (!ProcessSingleTransaction(block, processingOptions, receiptsTracer, blockToProduce, ref includedTx, consideredTx, i, currentTx)) + { + break; + } + } + + + foreach (Transaction currentTx in delayedTx) + { + // Check if we have gone over time or the payload has been requested + if (token.IsCancellationRequested) break; + + if (!ProcessSingleTransaction(block, processingOptions, receiptsTracer, blockToProduce, ref includedTx, consideredTx, i, currentTx)) + { + break; + } + } + + block.Header.TxRoot = TxTrie.CalculateRoot(includedTx.AsSpan()); + if (blockToProduce is not null) + { + blockToProduce.Transactions = includedTx.ToArray(); + } + return receiptsTracer.TxReceipts.ToArray(); + } + finally + { + includedTx.Dispose(); + } + } + + private bool ProcessSingleTransaction(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, BlockToProduce blockToProduce, ref ArrayPoolListRef includedTx, HashSet consideredTx, int i, Transaction currentTx) + { + TxAction action = ProcessTransaction(block, currentTx, i++, receiptsTracer, processingOptions, consideredTx); + if (action == TxAction.Stop) return false; + + consideredTx.Add(currentTx); + if (action == TxAction.Add) + { + includedTx.Add(currentTx); + if (blockToProduce is not null) + { + blockToProduce.TxByteLength += currentTx.GetLength(false); + } + } + + return true; + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockProductionEnvFactory.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockProductionEnvFactory.cs new file mode 100644 index 00000000000..93cc99b9121 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockProductionEnvFactory.cs @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; +using Nethermind.Core; +using Nethermind.State; +using System; +using System.Collections.Generic; +using System.Text; +using static Nethermind.Consensus.Processing.BlockProcessor; + +namespace Nethermind.Xdc; + +public class XdcBlockProductionEnvFactory(ILifetimeScope rootLifetime, IWorldStateManager worldStateManager, IBlockProducerTxSourceFactory txSourceFactory) + : BlockProducerEnvFactory(rootLifetime, worldStateManager, txSourceFactory) +{ + protected override ContainerBuilder ConfigureBuilder(ContainerBuilder builder) => + // Taiko does not seems to use `BlockProductionTransactionsExecutor` + base.ConfigureBuilder(builder) + .AddScoped(); +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockValidationModule.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockValidationModule.cs new file mode 100644 index 00000000000..fd1c82e23f2 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockValidationModule.cs @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Container; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class XdcBlockValidationModule : Module, IBlockValidationModule +{ + protected override void Load(ContainerBuilder builder) + { + builder.AddScoped(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockValidationTransactionExecutor.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockValidationTransactionExecutor.cs new file mode 100644 index 00000000000..653ddd7c1a9 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockValidationTransactionExecutor.cs @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain.Tracing; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Xdc.Spec; +using System; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Metrics = Nethermind.Evm.Metrics; + +namespace Nethermind.Xdc; + +internal class XdcBlockValidationTransactionExecutor(ITransactionProcessorAdapter txProcessorAdapter, IWorldState stateProvider, ISpecProvider specProvider) + : BlockProcessor.BlockValidationTransactionsExecutor(txProcessorAdapter, stateProvider) +{ + public override TxReceipt[] ProcessTransactions(Block block, ProcessingOptions processingOptions, BlockReceiptsTracer receiptsTracer, CancellationToken token) + { + Metrics.ResetBlockStats(); + + var spec = specProvider.GetXdcSpec(block.Header as XdcBlockHeader); + + + for (int i = 0; i < block.Transactions.Length; i++) + { + Transaction currentTx = block.Transactions[i]; + if (!currentTx.IsSpecialTransaction(spec)) + continue; + + ProcessTransaction(block, currentTx, i, receiptsTracer, processingOptions); + } + + for (int i = 0; i < block.Transactions.Length; i++) + { + Transaction currentTx = block.Transactions[i]; + if (currentTx.IsSpecialTransaction(spec)) + continue; + + ProcessTransaction(block, currentTx, i, receiptsTracer, processingOptions); + } + + return receiptsTracer.TxReceipts.ToArray(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs b/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs index 509ddcc3920..cfd594cedf6 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcExtensions.cs @@ -23,7 +23,10 @@ public static Signature Sign(this IEthereumEcdsa ecdsa, PrivateKey privateKey, X ValueHash256 hash = ValueKeccak.Compute(_headerDecoder.Encode(header, RlpBehaviors.ForSealing).Bytes); return ecdsa.Sign(privateKey, in hash); } - + public static bool IsSpecialTransaction(this Transaction currentTx, IXdcReleaseSpec spec) + { + return currentTx.To is not null && ((currentTx.To == spec.BlockSignersAddress) || (currentTx.To == spec.RandomizeSMCBinary)); + } public static Address RecoverVoteSigner(this IEthereumEcdsa ecdsa, Vote vote) { KeccakRlpStream stream = new(); diff --git a/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs b/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs index c17b6d1dc3a..7657676c6de 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcHotStuff.cs @@ -34,6 +34,7 @@ internal class XdcHotStuff : IBlockProducerRunner private readonly ITimeoutTimer _timeoutTimer; private readonly IProcessExitSource _processExit; private readonly ILogger _logger; + private readonly ISignTransactionManager _signTransactionManager; private CancellationTokenSource? _cancellationTokenSource; private Task? _runTask; @@ -46,6 +47,7 @@ internal class XdcHotStuff : IBlockProducerRunner private ulong _highestSelfMinedRound; private ulong _highestVotedRound; + public XdcHotStuff( IBlockTree blockTree, IXdcConsensusContext xdcContext, @@ -58,6 +60,7 @@ public XdcHotStuff( ISigner signer, ITimeoutTimer timeoutTimer, IProcessExitSource processExit, + ISignTransactionManager signTransactionManager, ILogManager logManager) { _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); @@ -69,6 +72,7 @@ public XdcHotStuff( _quorumCertificateManager = quorumCertificateManager ?? throw new ArgumentNullException(nameof(quorumCertificateManager)); _votesManager = votesManager ?? throw new ArgumentNullException(nameof(votesManager)); _signer = signer ?? throw new ArgumentNullException(nameof(signer)); + _signTransactionManager = signTransactionManager ?? throw new ArgumentNullException(nameof(signTransactionManager)); _timeoutTimer = timeoutTimer; _processExit = processExit; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); @@ -241,12 +245,18 @@ internal async Task RunRoundChecks(CancellationToken ct) { _highestSelfMinedRound = currentRound; Task blockBuilder = BuildAndProposeBlock(roundParent, currentRound, spec, ct); + + if ((roundParent.Number % spec.MergeSignRange == 0) || !(spec.TIP2019Block <= roundParent.Number)) + { + await _signTransactionManager.CreateTransactionSign(roundParent, spec); + } } if (spec.SwitchBlock < roundParent.Number) { await CommitCertificateAndVote(roundParent, epochInfo); } + } private XdcBlockHeader GetParentForRound() diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs index 0676b10e9b7..77ace69ae63 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcModule.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -8,12 +8,16 @@ using Nethermind.Blockchain.Headers; using Nethermind.Consensus; using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Producers; using Nethermind.Consensus.Validators; using Nethermind.Core; +using Nethermind.Core.Container; using Nethermind.Core.Specs; using Nethermind.Db; +using Nethermind.Evm.TransactionProcessing; using Nethermind.Init.Modules; using Nethermind.Specs.ChainSpecStyle; +using Nethermind.TxPool; using Nethermind.Xdc.Spec; namespace Nethermind.Xdc; @@ -21,6 +25,7 @@ namespace Nethermind.Xdc; public class XdcModule : Module { private const string SnapshotDbName = "Snapshots"; + private const string SignTxRandomizeDbName = "RandomValues"; protected override void Load(ContainerBuilder builder) { @@ -63,9 +68,16 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddDatabase(SnapshotDbName) .AddSingleton(CreateSnapshotManager) + .AddDatabase(SignTxRandomizeDbName) + .AddSingleton(CreateSignTransactionManager) .AddSingleton() .AddSingleton() .AddSingleton() + + // block processing + .AddSingleton() + .AddScoped() + .AddScoped() ; } @@ -73,5 +85,9 @@ private ISnapshotManager CreateSnapshotManager([KeyFilter(SnapshotDbName)] IDb d { return new SnapshotManager(db, blockTree, penaltyHandler); } + private ISignTransactionManager CreateSignTransactionManager([KeyFilter(SignTxRandomizeDbName)] IDb db, ISigner signer, ITxPool txPool) + { + return new SignTransactionManager(db, signer, txPool); + } } diff --git a/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs b/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs new file mode 100644 index 00000000000..388f1745b3b --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcTransactionProcessor.cs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Nethermind.Xdc; + +internal class XdcTransactionProcessor( + ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager) : TransactionProcessor(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) +{ + protected override TransactionResult ValidateStatic(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, in IntrinsicGas intrinsicGas) + { + var xdcSpec = spec as XdcReleaseSpec; + Address target = tx.To; + Address sender = tx.SenderAddress; + + if (xdcSpec.BlackListHFNumber <= header.Number) + { + if (IsBlackListed(xdcSpec, sender) || IsBlackListed(xdcSpec, target)) + { + // Skip processing special transactions if either sender or recipient is blacklisted + return TransactionResult.ContainsBlacklistedAddress; + } + } + + if (tx.IsSpecialTransaction(xdcSpec)) + { + if (target == xdcSpec.BlockSignersAddress) + { + if (tx.Data.Length < 68) + { + return TransactionResult.MalformedTransaction; + } + + UInt256 blkNumber = new UInt256(tx.Data.Span.Slice(4, 32), true); + if (blkNumber >= header.Number || blkNumber <= (header.Number - (xdcSpec.EpochLength * 2))) + { + // Invalid block number in special transaction data + return TransactionResult.MalformedTransaction; + } + + } + } + + return base.ValidateStatic(tx, header, spec, opts, intrinsicGas); + } + + private bool IsBlackListed(IXdcReleaseSpec spec, Address sender) + { + return spec.BlackListedAddresses.Contains(sender); + } + +}