Skip to content

Commit 11a8dad

Browse files
Change/use persisted pointer for fast header (#7855)
Co-authored-by: Lukasz Rozmej <[email protected]>
1 parent a831e5a commit 11a8dad

File tree

17 files changed

+338
-129
lines changed

17 files changed

+338
-129
lines changed

src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,7 +1139,7 @@ public void When_deleting_invalid_block_does_not_delete_blocks_that_are_not_its_
11391139
}
11401140

11411141
[Test, MaxTime(Timeout.MaxTestTime), TestCaseSource(nameof(SourceOfBSearchTestCases))]
1142-
public void Loads_lowest_inserted_header_correctly(long beginIndex, long insertedBlocks)
1142+
public void When_lowestInsertedHeaderWasNotPersisted_useBinarySearchToLoadLowestInsertedHeader(long beginIndex, long insertedBlocks)
11431143
{
11441144
long? expectedResult = insertedBlocks == 0L ? null : beginIndex - insertedBlocks + 1L;
11451145

@@ -1161,13 +1161,53 @@ public void Loads_lowest_inserted_header_correctly(long beginIndex, long inserte
11611161
tree.Insert(Build.A.BlockHeader.WithNumber(i).WithTotalDifficulty(i).TestObject);
11621162
}
11631163

1164-
BlockTree loadedTree = Build.A.BlockTree()
1164+
builder.MetadataDb.Delete(MetadataDbKeys.LowestInsertedFastHeaderHash);
1165+
1166+
tree = Build.A.BlockTree()
11651167
.WithDatabaseFrom(builder)
11661168
.WithSyncConfig(syncConfig)
11671169
.TestObject;
11681170

11691171
Assert.That(tree.LowestInsertedHeader?.Number, Is.EqualTo(expectedResult), "tree");
1170-
Assert.That(loadedTree.LowestInsertedHeader?.Number, Is.EqualTo(expectedResult), "loaded tree");
1172+
}
1173+
1174+
[Test]
1175+
public void When_lowestInsertedHeaderWasPersisted_doNot_useBinarySearchToLoadLowestInsertedHeader()
1176+
{
1177+
SyncConfig syncConfig = new()
1178+
{
1179+
FastSync = true,
1180+
PivotNumber = "105",
1181+
};
1182+
1183+
BlockTreeBuilder builder = Build.A
1184+
.BlockTree()
1185+
.WithSyncConfig(syncConfig);
1186+
BlockTree tree = builder.TestObject;
1187+
tree.SuggestBlock(Build.A.Block.Genesis.TestObject);
1188+
tree.RecalculateTreeLevels();
1189+
1190+
for (int i = 1; i < 100; i++)
1191+
{
1192+
tree.Insert(Build.A.BlockHeader.WithNumber(i).WithParent(tree.FindHeader(i - 1, BlockTreeLookupOptions.None)!).TestObject);
1193+
}
1194+
1195+
BlockTree loadedTree = Build.A.BlockTree()
1196+
.WithDatabaseFrom(builder)
1197+
.WithSyncConfig(syncConfig)
1198+
.TestObject;
1199+
1200+
Assert.That(tree.LowestInsertedHeader?.Number, Is.EqualTo(null));
1201+
Assert.That(loadedTree.LowestInsertedHeader?.Number, Is.EqualTo(null));
1202+
1203+
loadedTree.LowestInsertedHeader = tree.FindHeader(50, BlockTreeLookupOptions.None);
1204+
1205+
loadedTree = Build.A.BlockTree()
1206+
.WithDatabaseFrom(builder)
1207+
.WithSyncConfig(syncConfig)
1208+
.TestObject;
1209+
1210+
Assert.That(loadedTree.LowestInsertedHeader?.Number, Is.EqualTo(50));
11711211
}
11721212

11731213
[TestCase(5, 10)]
@@ -1352,7 +1392,6 @@ public void Loads_best_known_correctly_on_inserts_followed_by_suggests(long pivo
13521392
.TestObject;
13531393

13541394
Assert.That(tree.BestKnownNumber, Is.EqualTo(pivotNumber + 1), "tree");
1355-
Assert.That(tree.LowestInsertedHeader?.Number, Is.EqualTo(1), "loaded tree - lowest header");
13561395
Assert.That(loadedTree.BestKnownNumber, Is.EqualTo(pivotNumber + 1), "loaded tree");
13571396
}
13581397

src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,22 @@ private void LoadLowestInsertedBeaconHeader()
126126

127127
private void LoadLowestInsertedHeader()
128128
{
129-
long left = 1L;
130-
long right = _syncConfig.PivotNumberParsed;
129+
if (_metadataDb.KeyExists(MetadataDbKeys.LowestInsertedFastHeaderHash))
130+
{
131+
Hash256? headerHash = _metadataDb.Get(MetadataDbKeys.LowestInsertedFastHeaderHash)?
132+
.AsRlpStream().DecodeKeccak();
133+
_lowestInsertedHeader = FindHeader(headerHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded);
134+
}
135+
else
136+
{
137+
// Old style binary search.
138+
long left = 1L;
139+
long right = _syncConfig.PivotNumberParsed;
140+
141+
LowestInsertedHeader = BinarySearchBlockHeader(left, right, LevelExists, BinarySearchDirection.Down);
142+
}
131143

132-
LowestInsertedHeader = BinarySearchBlockHeader(left, right, LevelExists, BinarySearchDirection.Down);
144+
if (_logger.IsDebug) _logger.Debug($"Lowest inserted header set to {LowestInsertedHeader?.Number.ToString() ?? "null"}");
133145
}
134146

135147
private void LoadBestKnown()

src/Nethermind/Nethermind.Blockchain/BlockTree.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ public partial class BlockTree : IBlockTree
6161
public BlockHeader? BestSuggestedHeader { get; private set; }
6262

6363
public Block? BestSuggestedBody { get; private set; }
64-
public BlockHeader? LowestInsertedHeader { get; private set; }
64+
public BlockHeader? LowestInsertedHeader
65+
{
66+
get => _lowestInsertedHeader;
67+
set
68+
{
69+
_lowestInsertedHeader = value;
70+
_metadataDb.Set(MetadataDbKeys.LowestInsertedFastHeaderHash, Rlp.Encode(value?.Hash ?? value?.CalculateHash()).Bytes);
71+
}
72+
}
73+
74+
private BlockHeader? _lowestInsertedHeader;
75+
6576
public BlockHeader? BestSuggestedBeaconHeader { get; private set; }
6677

6778
public Block? BestSuggestedBeaconBody { get; private set; }
@@ -123,6 +134,9 @@ public BlockTree(
123134
DeleteBlocks(new Hash256(deletePointer));
124135
}
125136

137+
// Need to be here because it still need to run even if there are no genesis to store the null entry.
138+
LoadLowestInsertedHeader();
139+
126140
ChainLevelInfo? genesisLevel = LoadLevel(0);
127141
if (genesisLevel is not null)
128142
{
@@ -199,11 +213,6 @@ public AddBlockResult Insert(BlockHeader header, BlockTreeInsertHeaderOptions he
199213
bool isOnMainChain = (headerOptions & BlockTreeInsertHeaderOptions.NotOnMainChain) == 0;
200214
BlockInfo blockInfo = new(header.Hash, header.TotalDifficulty ?? 0);
201215

202-
if (header.Number < (LowestInsertedHeader?.Number ?? long.MaxValue))
203-
{
204-
LowestInsertedHeader = header;
205-
}
206-
207216
bool beaconInsert = (headerOptions & BlockTreeInsertHeaderOptions.BeaconHeaderMetadata) != 0;
208217
if (!beaconInsert)
209218
{

src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ public BlockTreeOverlay(IReadOnlyBlockTree baseTree, IBlockTree overlayTree)
3030
public BlockHeader? BestSuggestedHeader => _overlayTree.BestSuggestedHeader ?? _baseTree.BestSuggestedHeader;
3131
public Block? BestSuggestedBody => _overlayTree.BestSuggestedBody ?? _baseTree.BestSuggestedBody;
3232
public BlockHeader? BestSuggestedBeaconHeader => _overlayTree.BestSuggestedBeaconHeader ?? _baseTree.BestSuggestedBeaconHeader;
33-
public BlockHeader? LowestInsertedHeader => _overlayTree.LowestInsertedHeader ?? _baseTree.LowestInsertedHeader;
33+
public BlockHeader? LowestInsertedHeader
34+
{
35+
get => _overlayTree.LowestInsertedHeader ?? _baseTree.LowestInsertedHeader;
36+
set => _overlayTree.LowestInsertedHeader = value;
37+
}
3438

3539
public BlockHeader? LowestInsertedBeaconHeader
3640
{

src/Nethermind/Nethermind.Blockchain/IBlockTree.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public interface IBlockTree : IBlockFinder
4545
/// <summary>
4646
/// Lowest header added in reverse fast sync insert
4747
/// </summary>
48-
BlockHeader? LowestInsertedHeader { get; }
48+
BlockHeader? LowestInsertedHeader { get; set; }
4949

5050
/// <summary>
5151
/// Lowest header number added in reverse beacon sync insert. Used to determine if BeaconHeaderSync is completed.

src/Nethermind/Nethermind.Blockchain/ReadOnlyBlockTree.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ public ReadOnlyBlockTree(IBlockTree wrapped)
3030
public BlockHeader Genesis => _wrapped.Genesis;
3131
public BlockHeader BestSuggestedHeader => _wrapped.BestSuggestedHeader;
3232
public BlockHeader BestSuggestedBeaconHeader => _wrapped.BestSuggestedBeaconHeader;
33-
public BlockHeader LowestInsertedHeader => _wrapped.LowestInsertedHeader;
33+
public BlockHeader? LowestInsertedHeader
34+
{
35+
get => _wrapped.LowestInsertedHeader;
36+
set { }
37+
}
3438

3539
public long? BestPersistedState
3640
{

src/Nethermind/Nethermind.Db/MetadataDbKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ public static class MetadataDbKeys
1616
public const int UpdatedPivotData = 9;
1717
public const int ReceiptsBarrierWhenStarted = 10;
1818
public const int BodiesBarrierWhenStarted = 11;
19+
public const int LowestInsertedFastHeaderHash = 12;
1920
}
2021
}

src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ public async Task forkChoiceUpdatedV1_unknown_block_initiates_syncing()
6767
BestSuggestedHeader = chain.BlockTree.Genesis!,
6868
BestSuggestedBody = chain.BlockTree.FindBlock(0)!,
6969
BestKnownBeaconBlock = 0,
70-
LowestInsertedHeader = null,
7170
LowestInsertedBeaconHeader = null
7271
};
7372
AssertBlockTreePointers(chain.BlockTree, pointers);
@@ -86,7 +85,6 @@ public async Task forkChoiceUpdatedV1_unknown_block_initiates_syncing()
8685
block.Header.TotalDifficulty = 0;
8786
pointers.LowestInsertedBeaconHeader = block.Header;
8887
pointers.BestKnownBeaconBlock = block.Number;
89-
pointers.LowestInsertedHeader = block.Header;
9088
AssertBlockTreePointers(chain.BlockTree, pointers);
9189
AssertExecutionStatusNotChangedV1(chain.BlockFinder, block.Hash!, startingHead, startingHead);
9290
}
@@ -237,7 +235,6 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat
237235
BestSuggestedHeader = chain.BlockTree.Genesis!,
238236
BestSuggestedBody = chain.BlockTree.FindBlock(0)!,
239237
BestKnownBeaconBlock = 0,
240-
LowestInsertedHeader = null,
241238
LowestInsertedBeaconHeader = null
242239
};
243240
AssertBlockTreePointers(chain.BlockTree, pointers);
@@ -256,7 +253,6 @@ public async Task forkChoiceUpdatedV1_unknown_block_parent_while_syncing_initiat
256253
block.Header.TotalDifficulty = 0;
257254
pointers.LowestInsertedBeaconHeader = block.Header;
258255
pointers.BestKnownBeaconBlock = block.Number;
259-
pointers.LowestInsertedHeader = block.Header;
260256

261257
AssertBlockTreePointers(chain.BlockTree, pointers);
262258

@@ -762,7 +758,6 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync()
762758
BestSuggestedHeader = chain.BlockTree.Genesis!,
763759
BestSuggestedBody = chain.BlockTree.FindBlock(0)!,
764760
BestKnownBeaconBlock = 0,
765-
LowestInsertedHeader = null,
766761
LowestInsertedBeaconHeader = null
767762
};
768763
AssertBlockTreePointers(chain.BlockTree, pointers);
@@ -792,7 +787,6 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync()
792787
chain.BeaconSync.IsBeaconSyncFinished(pivotBlock.Header).Should().BeFalse();
793788
AssertBeaconPivotValues(chain.BeaconPivot!, pivotBlock.Header);
794789
pointers.LowestInsertedBeaconHeader = missingBlocks[^filledNum].Header;
795-
pointers.LowestInsertedHeader = missingBlocks[^filledNum].Header;
796790
pointers.BestKnownBeaconBlock = 9;
797791
AssertBlockTreePointers(chain.BlockTree, pointers);
798792
// finish rest of headers sync
@@ -803,7 +797,6 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_archive_sync()
803797

804798
// headers sync should be finished but not forwards beacon sync
805799
pointers.LowestInsertedBeaconHeader = missingBlocks[0].Header;
806-
pointers.LowestInsertedHeader = missingBlocks[0].Header;
807800
AssertBlockTreePointers(chain.BlockTree, pointers);
808801
chain.BeaconSync.ShouldBeInBeaconHeaders().Should().BeFalse();
809802
chain.BeaconSync.IsBeaconSyncHeadersFinished().Should().BeTrue();
@@ -913,7 +906,6 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_fast_sync()
913906
BestSuggestedHeader = chain.BlockTree.Genesis!,
914907
BestSuggestedBody = chain.BlockTree.FindBlock(0)!,
915908
BestKnownBeaconBlock = 0,
916-
LowestInsertedHeader = null,
917909
LowestInsertedBeaconHeader = null
918910
};
919911
AssertBlockTreePointers(chain.BlockTree, pointers);
@@ -940,7 +932,6 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_fast_sync()
940932
// verify correct pointers
941933
requests[0].TryGetBlock(out Block? destinationBlock);
942934
pointers.LowestInsertedBeaconHeader = destinationBlock!.Header;
943-
pointers.LowestInsertedHeader = destinationBlock.Header;
944935
pointers.BestKnownBeaconBlock = 13;
945936
AssertBlockTreePointers(chain.BlockTree, pointers);
946937
chain.BeaconSync!.ShouldBeInBeaconHeaders().Should().BeFalse();
@@ -1061,7 +1052,6 @@ private void AssertBlockTreePointers(
10611052
blockTree.BestSuggestedBody.Should().Be(pointers.BestSuggestedBody);
10621053
// TODO: post merge sync change to best beacon block
10631054
(blockTree.BestSuggestedBeaconHeader?.Number ?? 0).Should().Be(pointers.BestKnownBeaconBlock);
1064-
blockTree.LowestInsertedHeader.Should().BeEquivalentTo(pointers.LowestInsertedHeader);
10651055
blockTree.LowestInsertedBeaconHeader.Should().BeEquivalentTo(pointers.LowestInsertedBeaconHeader);
10661056
}
10671057

@@ -1079,7 +1069,6 @@ private class BlockTreePointers
10791069
public BlockHeader? BestSuggestedHeader;
10801070
public Block? BestSuggestedBody;
10811071
public long BestKnownBeaconBlock;
1082-
public BlockHeader? LowestInsertedHeader;
10831072
public BlockHeader? LowestInsertedBeaconHeader;
10841073
}
10851074
}

src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,16 @@ public sealed class BeaconHeadersSyncFeed : HeadersSyncFeed
3636
protected override bool AllHeadersDownloaded => (_blockTree.LowestInsertedBeaconHeader?.Number ?? long.MaxValue) <=
3737
_pivot.PivotDestinationNumber || _chainMerged;
3838

39-
protected override BlockHeader? LowestInsertedBlockHeader => _blockTree.LowestInsertedBeaconHeader;
39+
protected override BlockHeader? LowestInsertedBlockHeader
40+
{
41+
get => _blockTree.LowestInsertedBeaconHeader;
42+
set
43+
{
44+
// LowestInsertedBeaconHeader is set in blocktree when BeaconHeaderInsert is set.
45+
// TODO: Probably should move that logic here so that `LowestInsertedBeaconHeader` is set only once per batch.
46+
}
47+
}
48+
4049
protected override MeasuredProgress HeadersSyncProgressReport => _syncReport.BeaconHeaders;
4150

4251
protected override MeasuredProgress HeadersSyncQueueReport => _syncReport.BeaconHeadersInQueue;

src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828

2929
namespace Nethermind.Synchronization.Test.FastBlocks;
3030

31-
[Parallelizable(ParallelScope.Self)]
31+
[Parallelizable(ParallelScope.All)]
3232
public class FastHeadersSyncTests
3333
{
3434
[Test]
@@ -408,7 +408,8 @@ public async Task Can_resume_downloading_from_parent_of_lowest_inserted_header()
408408
public async Task Can_insert_all_good_headers_from_dependent_batch_with_missing_or_null_headers(int nullIndex, int count, int increment, bool shouldReport, bool useNulls)
409409
{
410410
var peerChain = CachedBlockTreeBuilder.OfLength(1000);
411-
var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = "1000", PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" };
411+
var pivotHeader = peerChain.FindHeader(998)!;
412+
var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number.ToString(), PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! };
412413

413414
IBlockTree localBlockTree = Build.A.BlockTree(peerChain.FindBlock(0, BlockTreeLookupOptions.None)!, null).WithSyncConfig(syncConfig).TestObject;
414415
const int lowestInserted = 999;
@@ -419,7 +420,7 @@ public async Task Can_insert_all_good_headers_from_dependent_batch_with_missing_
419420
report.FastBlocksHeaders.Returns(new MeasuredProgress());
420421

421422
ISyncPeerPool syncPeerPool = Substitute.For<ISyncPeerPool>();
422-
using HeadersSyncFeed feed = new(localBlockTree, syncPeerPool, syncConfig, report, LimboLogs.Instance);
423+
using HeadersSyncFeed feed = new(localBlockTree, syncPeerPool, syncConfig, report, new TestLogManager(LogLevel.Trace));
423424
feed.InitializeFeed();
424425
using HeadersSyncBatch? firstBatch = await feed.PrepareRequest();
425426
using HeadersSyncBatch? dependentBatch = await feed.PrepareRequest();
@@ -461,6 +462,60 @@ void FillBatch(HeadersSyncBatch batch, long start, bool applyNulls)
461462
syncPeerPool.Received(shouldReport ? 1 : 0).ReportBreachOfProtocol(Arg.Any<PeerInfo>(), Arg.Any<DisconnectReason>(), Arg.Any<string>());
462463
}
463464

465+
[Test]
466+
public async Task Does_not_download_persisted_header()
467+
{
468+
var peerChain = CachedBlockTreeBuilder.OfLength(1000);
469+
var pivotHeader = peerChain.FindHeader(999)!;
470+
var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number.ToString(), PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! };
471+
472+
IBlockTree localBlockTree = Build.A.BlockTree(peerChain.FindBlock(0, BlockTreeLookupOptions.None)!, null).WithSyncConfig(syncConfig).TestObject;
473+
474+
// Insert some chain
475+
for (int i = 0; i < 600; i++)
476+
{
477+
localBlockTree.SuggestHeader(peerChain.FindHeader(i)!).Should().Be(AddBlockResult.Added);
478+
}
479+
480+
ISyncPeerPool syncPeerPool = Substitute.For<ISyncPeerPool>();
481+
ISyncReport report = Substitute.For<ISyncReport>();
482+
report.HeadersInQueue.Returns(new MeasuredProgress());
483+
report.FastBlocksHeaders.Returns(new MeasuredProgress());
484+
using HeadersSyncFeed feed = new(localBlockTree, syncPeerPool, syncConfig, report, new TestLogManager(LogLevel.Trace));
485+
feed.InitializeFeed();
486+
487+
void FillBatch(HeadersSyncBatch batch)
488+
{
489+
batch.Response = Enumerable.Range((int)batch.StartNumber!, batch.RequestSize)
490+
.Select(i => peerChain.FindBlock(i, BlockTreeLookupOptions.None)!.Header)
491+
.ToPooledList<BlockHeader?>(batch.RequestSize);
492+
}
493+
494+
using HeadersSyncBatch batch1 = (await feed.PrepareRequest())!;
495+
batch1.StartNumber.Should().Be(808);
496+
497+
using HeadersSyncBatch batch2 = (await feed.PrepareRequest())!;
498+
batch2.StartNumber.Should().Be(616);
499+
500+
using HeadersSyncBatch batch3 = (await feed.PrepareRequest())!;
501+
batch3.StartNumber.Should().Be(424);
502+
503+
(await feed.PrepareRequest()).Should().Be(null);
504+
505+
FillBatch(batch1);
506+
FillBatch(batch2);
507+
FillBatch(batch3);
508+
509+
feed.HandleResponse(batch1);
510+
feed.HandleResponse(batch2);
511+
feed.HandleResponse(batch3);
512+
513+
// The dependency batch is processed during prepare request.
514+
(await feed.PrepareRequest()).Should().Be(null);
515+
516+
localBlockTree.LowestInsertedHeader?.Number.Should().Be(0);
517+
}
518+
464519
[Test]
465520
public async Task Will_never_lose_batch_on_invalid_batch()
466521
{

0 commit comments

Comments
 (0)