-
Notifications
You must be signed in to change notification settings - Fork 712
XDC Fix signing old blocks #12085
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
base: master
Are you sure you want to change the base?
XDC Fix signing old blocks #12085
Changes from all commits
98cc298
c8b433d
d72f410
1f956db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| // SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited | ||
| // SPDX-License-Identifier: LGPL-3.0-only | ||
|
|
||
| using System; | ||
| using Nethermind.Blockchain; | ||
| using Nethermind.Consensus; | ||
| using Nethermind.Core; | ||
| using Nethermind.Core.Crypto; | ||
| using Nethermind.Core.Specs; | ||
| using Nethermind.Core.Test.Builders; | ||
| using Nethermind.Logging; | ||
| using Nethermind.TxPool; | ||
| using Nethermind.Xdc.Spec; | ||
| using Nethermind.Xdc.Types; | ||
| using NSubstitute; | ||
| using NUnit.Framework; | ||
|
|
||
| namespace Nethermind.Xdc.Test; | ||
|
|
||
| [Parallelizable(ParallelScope.All)] | ||
| internal class SignTransactionManagerTests | ||
| { | ||
| // window = MergeSignRange(15) * MinePeriod(2) * MaxSignableBlockPeriods(2) = 60s. | ||
| [TestCase(0L, true)] | ||
| [TestCase(60L, true)] | ||
| [TestCase(61L, false)] | ||
| [TestCase(86_400L, false)] | ||
| public void OnBlockAddedToMain_SignsOnlyRecentHeadBlocks(long secondsBehind, bool shouldSign) | ||
| { | ||
| IXdcReleaseSpec spec = Substitute.For<IXdcReleaseSpec>(); | ||
| spec.MergeSignRange.Returns(15); | ||
| spec.MinePeriod.Returns(2); | ||
|
|
||
| ISpecProvider specProvider = Substitute.For<ISpecProvider>(); | ||
| specProvider.GetSpec(Arg.Any<ForkActivation>()).Returns(spec); | ||
|
|
||
| ManualTimestamper timestamper = new(); | ||
|
|
||
| XdcBlockHeader header = Build.A.XdcBlockHeader() | ||
| .WithNumber(spec.MergeSignRange) | ||
| .WithTimestamp((ulong)(timestamper.UnixTime.SecondsLong - secondsBehind)) | ||
| .WithExtraConsensusData(new ExtraFieldsV2(1, new QuorumCertificate(new BlockRoundInfo(Hash256.Zero, 0, 0), null, 0))) | ||
| .TestObject; | ||
| Block block = new(header); | ||
|
|
||
| ISigner signer = Substitute.For<ISigner>(); | ||
| signer.Address.Returns(TestItem.AddressA); | ||
| signer.TrySign(Arg.Any<Transaction>()).Returns(true); | ||
|
|
||
| ITxPool txPool = Substitute.For<ITxPool>(); | ||
| txPool.SubmitTx(Arg.Any<Transaction>(), Arg.Any<TxHandlingOptions>()).Returns(AcceptTxResult.Accepted); | ||
|
|
||
| IBlockTree blockTree = Substitute.For<IBlockTree>(); | ||
| blockTree.WasProcessed(block.Number, block.Hash!).Returns(true); | ||
| blockTree.FindBestSuggestedHeader().Returns(header); // bestSuggested == head => not syncing | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correction to previous review — the previous comment called this a no-op, but it is load-bearing. |
||
| blockTree.Head.Returns(block); | ||
|
|
||
| ISnapshotManager snapshotManager = Substitute.For<ISnapshotManager>(); | ||
| snapshotManager.GetSnapshotByBlockNumber(Arg.Any<long>(), Arg.Any<IXdcReleaseSpec>()) | ||
| .Returns(new Snapshot(block.Number, TestItem.KeccakA, [TestItem.AddressA])); | ||
|
|
||
| SignTransactionManager manager = new( | ||
| new Lazy<ISigner>(() => signer), | ||
| new Lazy<ITxPool>(() => txPool), | ||
| blockTree, snapshotManager, specProvider, timestamper, LimboLogs.Instance); | ||
| manager.Start(); | ||
|
|
||
| blockTree.BlockAddedToMain += Raise.EventWith(new BlockReplacementEventArgs(block)); | ||
|
|
||
| txPool.Received(shouldSign ? 1 : 0).SubmitTx(Arg.Any<Transaction>(), Arg.Any<TxHandlingOptions>()); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ internal class SignTransactionManager( | |||||||||||||
| IBlockTree blockTree, | ||||||||||||||
| ISnapshotManager snapshotManager, | ||||||||||||||
| ISpecProvider specProvider, | ||||||||||||||
| ITimestamper timestamper, | ||||||||||||||
| ILogManager logManager) : ISignTransactionManager, IStartable, IDisposable | ||||||||||||||
| { | ||||||||||||||
| // Lazy: ISigner and ITxPool are registered during InitializeBlockchain, after this class is instantiated. | ||||||||||||||
|
|
@@ -34,6 +35,7 @@ internal class SignTransactionManager( | |||||||||||||
| private readonly IBlockTree _blockTree = blockTree; | ||||||||||||||
| private readonly ISnapshotManager _snapshotManager = snapshotManager; | ||||||||||||||
| private readonly ISpecProvider _specProvider = specProvider; | ||||||||||||||
| private readonly ITimestamper _timestamper = timestamper; | ||||||||||||||
| private readonly ILogger _logger = logManager.GetClassLogger<SignTransactionManager>(); | ||||||||||||||
| private readonly AssociativeKeyCache<ValueHash256> _alreadySigned = new(128); | ||||||||||||||
|
|
||||||||||||||
|
|
@@ -76,6 +78,11 @@ private void OnBlockAddedToMain(object? sender, BlockReplacementEventArgs e) | |||||||||||||
| if (spec is null) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
| // Sign only recent head blocks; older ones are replayed during catch-up. | ||||||||||||||
| long window = spec.MergeSignRange * spec.MinePeriod * XdcConstants.MaxSignableBlockPeriods; | ||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low —
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Think we can probably extend the window here. All headers in current epoch should be signable. |
||||||||||||||
| if ((long)xdcHeader.Timestamp + window < _timestamper.UnixTime.SecondsLong) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
| if (xdcHeader.Number % spec.MergeSignRange != 0) | ||||||||||||||
| return; | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -60,4 +60,7 @@ internal static class XdcConstants | |||||||
|
|
||||||||
| // 4-byte selector + 32-byte block number + 32-byte block hash | ||||||||
| public const int SignTransactionDataLength = 68; | ||||||||
|
|
||||||||
| // Only sign recent head blocks. | ||||||||
| public const int MaxSignableBlockPeriods = 2; | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Low — the comment says "during sync" but the
Suggested change
|
||||||||
| } | ||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Low — since
blockTreeis an NSubstitute mock,IsSyncing()returnsdefault(i.e.isSyncing: false) without any explicit setup, so theFindBestSuggestedHeader().Returns(header)call doesn't actually influence the test outcome. The comment// bestSuggested == head => not syncingimplies a behavioral dependency that doesn't exist for a mock. The line could be removed, or if you want to document the intent, add a comment explainingIsSyncing()returns false by default on a mock.