Skip to content

Commit fe8adcc

Browse files
authored
Improve Block related caching (#9571)
* Don't recache if from cache * Improve ClockCache fast path * Use smaller keys for Block and Header caches * Pass via in * Add number cache * formatting * sp * Actually use cache * Remove duplicated method * Tweak * Change hash compare * Break test fix speed * Feedback * Test fix * null refs * Fall back to prior implementation * Restore test * Less test changes * Also do reorged block * null ref * Speed up forkchoice
1 parent 1c45afd commit fe8adcc

21 files changed

Lines changed: 231 additions & 110 deletions

File tree

src/Nethermind/Nethermind.Blockchain.Test/BlockhashProviderTests.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited
22
// SPDX-License-Identifier: LGPL-3.0-only
33

4+
using System;
45
using FluentAssertions;
56
using Nethermind.Blockchain.Blocks;
67
using Nethermind.Blockchain.Find;
@@ -9,12 +10,11 @@
910
using Nethermind.Core.Specs;
1011
using Nethermind.Core.Test;
1112
using Nethermind.Core.Test.Builders;
13+
using Nethermind.Evm.State;
1214
using Nethermind.Logging;
1315
using Nethermind.Specs;
1416
using Nethermind.Specs.Forks;
1517
using Nethermind.Specs.Test;
16-
using Nethermind.Evm.State;
17-
using Nethermind.State;
1818
using NUnit.Framework;
1919

2020
namespace Nethermind.Blockchain.Test;
@@ -80,20 +80,21 @@ public void Can_lookup_correctly_on_disconnected_main_chain()
8080
BlockhashProvider provider = CreateBlockHashProvider(tree, Frontier.Instance);
8181

8282
BlockHeader notCanonParent = tree.FindHeader(chainLength - 4, BlockTreeLookupOptions.None)!;
83-
BlockHeader expectedHeader = tree.FindHeader(chainLength - 3, BlockTreeLookupOptions.None)!;
83+
Block expected = tree.FindBlock(chainLength - 3, BlockTreeLookupOptions.None)!;
84+
8485
Block headParent = tree.FindBlock(chainLength - 2, BlockTreeLookupOptions.None)!;
8586
Block head = tree.FindBlock(chainLength - 1, BlockTreeLookupOptions.None)!;
8687

8788
Block branch = Build.A.Block.WithParent(notCanonParent).WithTransactions(Build.A.Transaction.TestObject).TestObject;
8889
tree.Insert(branch, BlockTreeInsertBlockOptions.SaveHeader).Should().Be(AddBlockResult.Added);
8990
tree.UpdateMainChain(branch); // Update branch
9091

91-
tree.UpdateMainChain([headParent, head], true); // Update back to original again, but skipping the branch block.
92+
tree.UpdateMainChain([expected, headParent, head], true); // Update back to original again, but skipping the branch block.
9293

9394
Block current = Build.A.Block.WithParent(head).TestObject; // At chainLength
9495

9596
Hash256? result = provider.GetBlockhash(current.Header, chainLength - 3);
96-
Assert.That(result, Is.EqualTo(expectedHeader.Hash));
97+
Assert.That(result, Is.EqualTo(expected.Header.Hash));
9798
}
9899

99100
[Test, MaxTime(Timeout.MaxTestTime)]

src/Nethermind/Nethermind.Blockchain.Test/Blocks/HeaderStoreTests.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class HeaderStoreTests
1717
[Test]
1818
public void TestCanStoreAndGetHeader()
1919
{
20-
HeaderStore store = new(new MemDb(), new MemDb());
20+
IHeaderStore store = new HeaderStore(new MemDb(), new MemDb());
2121

2222
BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject;
2323
BlockHeader header2 = Build.A.BlockHeader.WithNumber(102).TestObject;
@@ -39,7 +39,7 @@ public void TestCanStoreAndGetHeader()
3939
public void TestCanReadHeaderStoredWithHash()
4040
{
4141
IDb headerDb = new MemDb();
42-
HeaderStore store = new(headerDb, new MemDb());
42+
IHeaderStore store = new HeaderStore(headerDb, new MemDb());
4343

4444
BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject;
4545
headerDb.Set(header.Hash!, new HeaderDecoder().Encode(header).Bytes);
@@ -50,7 +50,7 @@ public void TestCanReadHeaderStoredWithHash()
5050
[Test]
5151
public void TestCanReadCacheHeader()
5252
{
53-
HeaderStore store = new(new MemDb(), new MemDb());
53+
IHeaderStore store = new HeaderStore(new MemDb(), new MemDb());
5454

5555
BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject;
5656
store.Cache(header);
@@ -60,7 +60,7 @@ public void TestCanReadCacheHeader()
6060
[Test]
6161
public void TestCanDeleteHeader()
6262
{
63-
HeaderStore store = new(new MemDb(), new MemDb());
63+
IHeaderStore store = new HeaderStore(new MemDb(), new MemDb());
6464
BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject;
6565
store.Insert(header);
6666
store.Delete(header.Hash!);
@@ -72,7 +72,7 @@ public void TestCanDeleteHeader()
7272
public void TestCanDeleteHeaderStoredWithHash()
7373
{
7474
IDb headerDb = new MemDb();
75-
HeaderStore store = new(headerDb, new MemDb());
75+
IHeaderStore store = new HeaderStore(headerDb, new MemDb());
7676

7777
BlockHeader header = Build.A.BlockHeader.WithNumber(100).TestObject;
7878
headerDb.Set(header.Hash!, new HeaderDecoder().Encode(header).Bytes);

src/Nethermind/Nethermind.Blockchain/BlockTree.cs

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,15 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options
559559
return blockHash is null ? null : FindHeader(blockHash, options, blockNumber: number);
560560
}
561561

562-
public Hash256? FindBlockHash(long blockNumber) => GetBlockHashOnMainOrBestDifficultyHash(blockNumber);
562+
public Hash256? FindBlockHash(long blockNumber)
563+
{
564+
Hash256? blockHash = _headerStore.GetBlockHash(blockNumber);
565+
if (blockHash is not null)
566+
{
567+
return blockHash;
568+
}
569+
return GetBlockHashOnMainOrBestDifficultyHash(blockNumber);
570+
}
563571

564572
public bool HasBlock(long blockNumber, Hash256 blockHash) => _blockStore.HasBlock(blockNumber, blockHash);
565573

@@ -571,7 +579,17 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options
571579
return null;
572580
}
573581

574-
BlockHeader? header = _headerStore.Get(blockHash, shouldCache: false, blockNumber: blockNumber);
582+
BlockHeader? header = null;
583+
if ((options & BlockTreeLookupOptions.RequireCanonical) == 0)
584+
{
585+
header = _headerStore.GetFromCache(blockHash);
586+
if (header is not null && (!blockNumber.HasValue || header.Number == blockNumber.Value))
587+
{
588+
return header;
589+
}
590+
}
591+
592+
header = _headerStore.Get(blockHash, out bool fromCache, shouldCache: false, blockNumber: blockNumber);
575593
if (header is null)
576594
{
577595
bool allowInvalid = (options & BlockTreeLookupOptions.AllowInvalid) == BlockTreeLookupOptions.AllowInvalid;
@@ -588,6 +606,7 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options
588606
bool createLevelIfMissing = (options & BlockTreeLookupOptions.DoNotCreateLevelIfMissing) == BlockTreeLookupOptions.None;
589607
bool requiresCanonical = (options & BlockTreeLookupOptions.RequireCanonical) == BlockTreeLookupOptions.RequireCanonical;
590608

609+
bool isMainChain = false;
591610
if ((totalDifficultyNeeded && header.TotalDifficulty is null) || requiresCanonical)
592611
{
593612
(BlockInfo blockInfo, ChainLevelInfo level) = LoadInfo(header.Number, header.Hash, true);
@@ -614,16 +633,16 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options
614633
SetTotalDifficultyFromBlockInfo(header, blockInfo);
615634
}
616635

636+
isMainChain = level?.MainChainBlock?.BlockHash?.Equals(blockHash) == true;
617637
if (requiresCanonical)
618638
{
619-
bool isMain = level.MainChainBlock?.BlockHash?.Equals(blockHash) == true;
620-
header = isMain ? header : null;
639+
header = isMainChain ? header : null;
621640
}
622641
}
623642

624-
if (header is not null && ShouldCache(header.Number))
643+
if (header is not null && !fromCache && ShouldCache(header.Number))
625644
{
626-
_headerStore.Cache(header);
645+
_headerStore.Cache(header, isMainChain);
627646
}
628647

629648
return header;
@@ -650,11 +669,6 @@ public AddBlockResult SuggestBlock(Block block, BlockTreeSuggestOptions options
650669
return null;
651670
}
652671

653-
public Hash256? FindHash(long number)
654-
{
655-
return GetBlockHashOnMainOrBestDifficultyHash(number);
656-
}
657-
658672
public IOwnedReadOnlyList<BlockHeader> FindHeaders(Hash256? blockHash, int numberOfBlocks, int skip, bool reverse)
659673
{
660674
if (numberOfBlocks == 0)
@@ -760,7 +774,9 @@ as it does not require the step of resolving number -> hash */
760774

761775
if (level.HasBlockOnMainChain)
762776
{
763-
return level.BlockInfos[0].BlockHash;
777+
Hash256 blockHash = level.BlockInfos[0].BlockHash;
778+
_headerStore.CacheBlockHash(blockNumber, blockHash);
779+
return blockHash;
764780
}
765781

766782
UInt256 bestDifficultySoFar = UInt256.Zero;
@@ -944,11 +960,9 @@ public void MarkChainAsProcessed(IReadOnlyList<Block> blocks)
944960
for (int i = 0; i < blocks.Count; i++)
945961
{
946962
Block block = blocks[i];
947-
if (ShouldCache(block.Number))
948-
{
949-
_blockStore.Cache(block);
950-
_headerStore.Cache(block.Header);
951-
}
963+
// Header also updates the number <-> hash mapping so always update
964+
_blockStore.Cache(block);
965+
_headerStore.Cache(block.Header, isMainChain: true);
952966

953967
ChainLevelInfo? level = LoadLevel(block.Number);
954968
int? index = (level?.FindIndex(block.Hash)) ?? throw new InvalidOperationException($"Cannot mark unknown block {block.ToString(Block.Format.FullHashAndNumber)} as processed");
@@ -1013,12 +1027,9 @@ public void UpdateMainChain(IReadOnlyList<Block> blocks, bool wereProcessed, boo
10131027
for (int i = 0; i < blocks.Count; i++)
10141028
{
10151029
Block block = blocks[i];
1016-
if (ShouldCache(block.Number))
1017-
{
1018-
_blockStore.Cache(block);
1019-
_headerStore.Cache(block.Header);
1020-
}
1021-
1030+
_blockStore.Cache(block);
1031+
// Header also updates the number <-> hash mapping so always update
1032+
_headerStore.Cache(block.Header, wereProcessed);
10221033
// we only force update head block for last block in processed blocks
10231034
bool lastProcessedBlock = i == blocks.Count - 1;
10241035

@@ -1435,12 +1446,23 @@ private bool ShouldCache(long number)
14351446
}
14361447

14371448
Block? block = null;
1449+
if ((options & BlockTreeLookupOptions.RequireCanonical) == 0)
1450+
{
1451+
block = _blockStore.GetFromCache(blockHash);
1452+
if (block is not null && block.TotalDifficulty.HasValue && (!blockNumber.HasValue || block.Number == blockNumber.Value))
1453+
{
1454+
return block;
1455+
}
1456+
}
1457+
1458+
bool fromCache = false;
14381459
blockNumber ??= _headerStore.GetBlockNumber(blockHash);
14391460
if (blockNumber is not null)
14401461
{
14411462
block = _blockStore.Get(
14421463
blockNumber.Value,
14431464
blockHash,
1465+
out fromCache,
14441466
(options & BlockTreeLookupOptions.ExcludeTxHashes) != 0 ? RlpBehaviors.ExcludeHashes : RlpBehaviors.None,
14451467
shouldCache: false);
14461468
}
@@ -1463,6 +1485,7 @@ private bool ShouldCache(long number)
14631485
bool requiresCanonical = (options & BlockTreeLookupOptions.RequireCanonical) ==
14641486
BlockTreeLookupOptions.RequireCanonical;
14651487

1488+
bool isMainChain = false;
14661489
if ((totalDifficultyNeeded && block.TotalDifficulty is null) || requiresCanonical)
14671490
{
14681491
(BlockInfo blockInfo, ChainLevelInfo level) = LoadInfo(block.Number, block.Hash, true);
@@ -1489,17 +1512,17 @@ private bool ShouldCache(long number)
14891512
SetTotalDifficultyFromBlockInfo(block.Header, blockInfo);
14901513
}
14911514

1515+
isMainChain = level?.MainChainBlock?.BlockHash.Equals(blockHash) == true;
14921516
if (requiresCanonical)
14931517
{
1494-
bool isMain = level.MainChainBlock?.BlockHash.Equals(blockHash) == true;
1495-
block = isMain ? block : null;
1518+
block = isMainChain ? block : null;
14961519
}
14971520
}
14981521

1499-
if (block is not null && ShouldCache(block.Number))
1522+
if (block is not null && !fromCache && ShouldCache(block.Number))
15001523
{
15011524
_blockStore.Cache(block);
1502-
_headerStore.Cache(block.Header);
1525+
_headerStore.Cache(block.Header, isMainChain);
15031526
}
15041527

15051528
return block;

src/Nethermind/Nethermind.Blockchain/BlockTreeOverlay.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public void UpdateMainChain(IReadOnlyList<Block> blocks, bool wereProcessed, boo
108108

109109
public BlockInfo FindCanonicalBlockInfo(long blockNumber) => _overlayTree.FindCanonicalBlockInfo(blockNumber) ?? _baseTree.FindCanonicalBlockInfo(blockNumber);
110110

111-
public Hash256 FindHash(long blockNumber) => _overlayTree.FindHash(blockNumber) ?? _baseTree.FindHash(blockNumber);
111+
public Hash256 FindBlockHash(long blockNumber) => _overlayTree.FindBlockHash(blockNumber) ?? _baseTree.FindBlockHash(blockNumber);
112112

113113
public IOwnedReadOnlyList<BlockHeader> FindHeaders(Hash256 hash, int numberOfBlocks, int skip, bool reverse)
114114
{
@@ -268,9 +268,6 @@ public bool HasBlock(long blockNumber, Hash256 blockHash) =>
268268
public BlockHeader? FindHeader(long blockNumber, BlockTreeLookupOptions options) =>
269269
_overlayTree.FindHeader(blockNumber, options) ?? _baseTree.FindHeader(blockNumber, options);
270270

271-
public Hash256? FindBlockHash(long blockNumber) =>
272-
_overlayTree.FindBlockHash(blockNumber) ?? _baseTree.FindBlockHash(blockNumber);
273-
274271
public bool IsMainChain(BlockHeader blockHeader) =>
275272
_baseTree.IsMainChain(blockHeader) || _overlayTree.IsMainChain(blockHeader);
276273

src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ public BlockhashProvider(IBlockFinder blockTree, ISpecProvider specProvider, IWo
4646
return null;
4747
}
4848

49+
return (currentBlock.ParentHash == _blockTree.HeadHash || currentBlock.ParentHash == _blockTree.Head?.ParentHash) ?
50+
_blockTree.FindBlockHash(number) :
51+
GetBlockHashFromNonHeadParent(currentBlock, number);
52+
}
53+
54+
private Hash256 GetBlockHashFromNonHeadParent(BlockHeader currentBlock, long number)
55+
{
4956
BlockHeader header = _blockTree.FindParentHeader(currentBlock, BlockTreeLookupOptions.TotalDifficultyNotNeeded) ??
5057
throw new InvalidDataException("Parent header cannot be found when executing BLOCKHASH operation");
5158

src/Nethermind/Nethermind.Blockchain/Blocks/BlockStore.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ public class BlockStore([KeyFilter(DbNames.Blocks)] IDb blockDb, IHeaderDecoder
1818
private readonly BlockDecoder _blockDecoder = new(headerDecoder ?? new HeaderDecoder());
1919
public const int CacheSize = 128 + 32;
2020

21-
private readonly ClockCache<ValueHash256, Block>
22-
_blockCache = new(CacheSize);
21+
private readonly ClockCache<Hash256AsKey, Block> _blockCache = new(CacheSize);
2322

2423
public void SetMetadata(byte[] key, byte[] value)
2524
{
@@ -67,9 +66,16 @@ public void Delete(long blockNumber, Hash256 blockHash)
6766

6867
public Block? Get(long blockNumber, Hash256 blockHash, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = false)
6968
{
70-
Block? b = blockDb.Get(blockNumber, blockHash, _blockDecoder, _blockCache, rlpBehaviors, shouldCache);
69+
Block? b = blockDb.Get(blockNumber, blockHash, _blockDecoder, out _, _blockCache, rlpBehaviors, shouldCache);
7170
if (b is not null) return b;
72-
return blockDb.Get(blockHash, _blockDecoder, _blockCache, rlpBehaviors, shouldCache);
71+
return blockDb.Get(blockHash, _blockDecoder, out _, _blockCache, rlpBehaviors, shouldCache);
72+
}
73+
74+
public Block? Get(long blockNumber, Hash256 blockHash, out bool fromCache, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = false)
75+
{
76+
Block? b = blockDb.Get(blockNumber, blockHash, _blockDecoder, out fromCache, _blockCache, rlpBehaviors, shouldCache);
77+
if (b is not null) return b;
78+
return blockDb.Get(blockHash, _blockDecoder, out fromCache, _blockCache, rlpBehaviors, shouldCache);
7379
}
7480

7581
public byte[]? GetRlp(long blockNumber, Hash256 blockHash)
@@ -96,4 +102,7 @@ public void Cache(Block block)
96102
{
97103
_blockCache.Set(block.Hash, block);
98104
}
105+
106+
public Block? GetFromCache(Hash256 blockHash)
107+
=> _blockCache.Get(blockHash);
99108
}

src/Nethermind/Nethermind.Blockchain/Blocks/IBlockStore.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ public interface IBlockStore
1515
{
1616
void Insert(Block block, WriteFlags writeFlags = WriteFlags.None);
1717
void Delete(long blockNumber, Hash256 blockHash);
18-
Block? Get(long blockNumber, Hash256 blockHash, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = true);
18+
Block? Get(long blockNumber, Hash256 blockHash, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = true)
19+
=> Get(blockNumber, blockHash, out _, rlpBehaviors, shouldCache);
20+
Block? Get(long blockNumber, Hash256 blockHash, out bool fromCache, RlpBehaviors rlpBehaviors = RlpBehaviors.None, bool shouldCache = true);
1921
byte[]? GetRlp(long blockNumber, Hash256 blockHash);
2022
ReceiptRecoveryBlock? GetReceiptRecoveryBlock(long blockNumber, Hash256 blockHash);
2123
void Cache(Block block);
24+
Block? GetFromCache(Hash256 blockHash);
2225
bool HasBlock(long blockNumber, Hash256 blockHash);
2326
}

0 commit comments

Comments
 (0)