Skip to content

Commit 734324a

Browse files
authored
Flush chain repo and wait for store to catch up before flushing conse… (#223)
* Flush chain repo and wait for store to catch up before flushing consensus * Fix test
1 parent b2a9869 commit 734324a

8 files changed

Lines changed: 207 additions & 106 deletions

File tree

src/Blockcore/Base/ChainRepository.cs

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public class ChainRepository : IChainRepository
3030

3131
private BlockLocator locator;
3232

33+
private object lockObj;
34+
3335
public Network Network { get; }
3436

3537
public ChainRepository(ILoggerFactory loggerFactory, IChainStore chainStore, Network network)
@@ -38,6 +40,7 @@ public ChainRepository(ILoggerFactory loggerFactory, IChainStore chainStore, Net
3840

3941
this.chainStore = chainStore;
4042
this.Network = network;
43+
this.lockObj = new object();
4144

4245
this.logger = loggerFactory.CreateLogger(this.GetType().FullName);
4346
}
@@ -47,39 +50,42 @@ public Task<ChainedHeader> LoadAsync(ChainedHeader genesisHeader)
4750
{
4851
Task<ChainedHeader> task = Task.Run(() =>
4952
{
50-
ChainedHeader tip = null;
51-
52-
ChainData data = this.chainStore.GetChainData(0);
53-
54-
if (data == null)
53+
lock (this.lockObj)
5554
{
56-
genesisHeader.SetChainStore(this.chainStore);
57-
return genesisHeader;
58-
}
55+
ChainedHeader tip = null;
5956

60-
Guard.Assert(data.Hash == genesisHeader.HashBlock); // can't swap networks
61-
62-
int index = 0;
63-
while (true)
64-
{
65-
data = this.chainStore.GetChainData((index));
57+
ChainData data = this.chainStore.GetChainData(0);
6658

6759
if (data == null)
68-
break;
69-
70-
tip = new ChainedHeader(data.Hash, data.Work, tip);
71-
if (tip.Height == 0) tip.SetChainStore(this.chainStore);
72-
index++;
73-
}
74-
75-
if (tip == null)
76-
{
77-
genesisHeader.SetChainStore(this.chainStore);
78-
tip = genesisHeader;
60+
{
61+
genesisHeader.SetChainStore(this.chainStore);
62+
return genesisHeader;
63+
}
64+
65+
Guard.Assert(data.Hash == genesisHeader.HashBlock); // can't swap networks
66+
67+
int index = 0;
68+
while (true)
69+
{
70+
data = this.chainStore.GetChainData((index));
71+
72+
if (data == null)
73+
break;
74+
75+
tip = new ChainedHeader(data.Hash, data.Work, tip);
76+
if (tip.Height == 0) tip.SetChainStore(this.chainStore);
77+
index++;
78+
}
79+
80+
if (tip == null)
81+
{
82+
genesisHeader.SetChainStore(this.chainStore);
83+
tip = genesisHeader;
84+
}
85+
86+
this.locator = tip.GetLocator();
87+
return tip;
7988
}
80-
81-
this.locator = tip.GetLocator();
82-
return tip;
8389
});
8490

8591
return task;
@@ -92,26 +98,29 @@ public Task SaveAsync(ChainIndexer chainIndexer)
9298

9399
Task task = Task.Run(() =>
94100
{
95-
ChainedHeader fork = this.locator == null ? null : chainIndexer.FindFork(this.locator);
96-
ChainedHeader tip = chainIndexer.Tip;
97-
ChainedHeader toSave = tip;
98-
99-
var headers = new List<ChainedHeader>();
100-
while (toSave != fork)
101+
lock (this.lockObj)
101102
{
102-
headers.Add(toSave);
103-
toSave = toSave.Previous;
103+
ChainedHeader fork = this.locator == null ? null : chainIndexer.FindFork(this.locator);
104+
ChainedHeader tip = chainIndexer.Tip;
105+
ChainedHeader toSave = tip;
106+
107+
var headers = new List<ChainedHeader>();
108+
while (toSave != fork)
109+
{
110+
headers.Add(toSave);
111+
toSave = toSave.Previous;
112+
}
113+
114+
var items = headers.OrderBy(b => b.Height).Select(h => new ChainDataItem
115+
{
116+
Height = h.Height,
117+
Data = new ChainData { Hash = h.HashBlock, Work = h.ChainWorkBytes }
118+
});
119+
120+
this.chainStore.PutChainData(items);
121+
122+
this.locator = tip.GetLocator();
104123
}
105-
106-
var items = headers.OrderBy(b => b.Height).Select(h => new ChainDataItem
107-
{
108-
Height = h.Height,
109-
Data = new ChainData { Hash = h.HashBlock, Work = h.ChainWorkBytes }
110-
});
111-
112-
this.chainStore.PutChainData(items);
113-
114-
this.locator = tip.GetLocator();
115124
});
116125

117126
return task;

src/Features/Blockcore.Features.Consensus/CoinViews/CachedCoinView.cs

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Text;
5+
using System.Threading.Tasks;
6+
using Blockcore.Base;
57
using Blockcore.Configuration.Settings;
68
using Blockcore.Consensus;
79
using Blockcore.Features.Consensus.CoinViews.Coindb;
@@ -130,7 +132,16 @@ public long GetScriptSize
130132

131133
private readonly Random random;
132134

133-
public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null)
135+
public CachedCoinView(
136+
Network network,
137+
ICheckpoints checkpoints,
138+
ICoindb coindb,
139+
IDateTimeProvider dateTimeProvider,
140+
ILoggerFactory loggerFactory,
141+
INodeStats nodeStats,
142+
ConsensusSettings consensusSettings,
143+
StakeChainStore stakeChainStore = null,
144+
IRewindDataIndexCache rewindDataIndexCache = null)
134145
{
135146
Guard.NotNull(coindb, nameof(CachedCoinView.coindb));
136147

@@ -303,37 +314,43 @@ private void TryEvictCacheLocked()
303314
}
304315
}
305316

317+
/// <summary>
318+
/// Check if periodic flush is required.
319+
/// The conditions to flash the cache are if <see cref="CacheFlushTimeIntervalSeconds"/> is elapsed
320+
/// or if <see cref="MaxCacheSizeBytes"/> is reached.
321+
/// </summary>
322+
/// <returns>True if the coinview needs to flush</returns>
323+
public bool ShouldFlush()
324+
{
325+
DateTime now = this.dateTimeProvider.GetUtcNow();
326+
bool flushTimeLimit = (now - this.lastCacheFlushTime).TotalSeconds >= this.CacheFlushTimeIntervalSeconds;
327+
328+
// The size of the cache was reached and most likely TryEvictCacheLocked didn't work
329+
// so the cache is polluted with flushable items, then we flush anyway.
330+
331+
long totalBytes = this.cacheSizeBytes + this.rewindDataSizeBytes;
332+
bool flushSizeLimit = totalBytes > this.MaxCacheSizeBytes;
333+
334+
if (!flushTimeLimit && !flushSizeLimit)
335+
{
336+
return false;
337+
}
338+
339+
this.logger.LogDebug("Flushing, reasons flushTimeLimit={0} flushSizeLimit={1}.", flushTimeLimit, flushSizeLimit);
340+
341+
return true;
342+
}
343+
306344
/// <summary>
307345
/// Finds all changed records in the cache and persists them to the underlying coinview.
308346
/// </summary>
309347
/// <param name="force"><c>true</c> to enforce flush, <c>false</c> to flush only if <see cref="lastCacheFlushTime"/> is older than <see cref="CacheFlushTimeIntervalSeconds"/>.</param>
310-
/// <remarks>
311-
/// WARNING: This method can only be run from <see cref="ConsensusLoop.Execute(System.Threading.CancellationToken)"/> thread context
312-
/// or when consensus loop is stopped. Otherwise, there is a risk of race condition when the consensus loop accepts new block.
313-
/// </remarks>
314348
public void Flush(bool force = true)
315349
{
316350
if (!force)
317351
{
318-
// Check if periodic flush is reuired.
319-
// Ideally this will flush less frequent and always be behind
320-
// blockstore which is currently set to 17 sec.
321-
322-
DateTime now = this.dateTimeProvider.GetUtcNow();
323-
bool flushTimeLimit = (now - this.lastCacheFlushTime).TotalSeconds >= this.CacheFlushTimeIntervalSeconds;
324-
325-
// The size of the cache was reached and most likely TryEvictCacheLocked didn't work
326-
// so the cahces is pulledted with flushable items, then we flush anyway.
327-
328-
long totalBytes = this.cacheSizeBytes + this.rewindDataSizeBytes;
329-
bool flushSizeLimit = totalBytes > this.MaxCacheSizeBytes;
330-
331-
if (!flushTimeLimit && !flushSizeLimit)
332-
{
352+
if (!this.ShouldFlush())
333353
return;
334-
}
335-
336-
this.logger.LogDebug("Flushing, reasons flushTimeLimit={0} flushSizeLimit={1}.", flushTimeLimit, flushSizeLimit);
337354
}
338355

339356
// Before flushing the coinview persist the stake store
@@ -408,7 +425,7 @@ public void SaveChanges(IList<UnspentOutput> outputs, HashHeightPair oldBlockHas
408425
if (!this.cachedUtxoItems.TryGetValue(output.OutPoint, out CacheItem cacheItem))
409426
{
410427
// Add outputs to cache, this will happen for two cases
411-
// 1. if a chaced item was evicted
428+
// 1. if a cached item was evicted
412429
// 2. for new outputs that are added
413430

414431
if (output.CreatedFromBlock)
@@ -428,8 +445,8 @@ public void SaveChanges(IList<UnspentOutput> outputs, HashHeightPair oldBlockHas
428445
}
429446
else
430447
{
431-
// This can happen if the cashe item was evicted while
432-
// the block was being processed, fetch the outut again from disk.
448+
// This can happen if the cached item was evicted while
449+
// the block was being processed, fetch the output again from disk.
433450

434451
this.logger.LogDebug("Outpoint '{0}' is not found in cache, creating it.", output.OutPoint);
435452

@@ -521,12 +538,12 @@ public void SaveChanges(IList<UnspentOutput> outputs, HashHeightPair oldBlockHas
521538

522539
this.logger.LogDebug("Coin override alllowed for utxo '{0}'.", cacheItem.OutPoint);
523540

524-
// Deduct the crurrent script size form the
541+
// Deduct the current script size form the
525542
// total cache size, it will be added again later.
526543
this.cacheSizeBytes -= cacheItem.GetScriptSize;
527544

528-
// Clear this in order to calculate the cache sie
529-
// this will get set later when overriden
545+
// Clear this in order to calculate the cache size
546+
// this will get set later when overridden
530547
cacheItem.Coins = null;
531548
}
532549

@@ -573,27 +590,11 @@ public void SaveChanges(IList<UnspentOutput> outputs, HashHeightPair oldBlockHas
573590
// When cache is flushed the rewind data will allow to rewind the node up to the
574591
// number of rewind blocks.
575592
// TODO: move rewind data to use block store.
576-
// Rewind data can go away all togetehr if the node uses the blocks in block store
593+
// Rewind data can go away all together if the node uses the blocks in block store
577594
// to get the rewind information, blockstore persists much more frequent then coin cache
578595
// So using block store for rewinds is not entirely impossible.
579596

580-
uint rewindDataWindow = 10;
581-
582-
if (this.blockHash.Height >= this.lastCheckpointHeight)
583-
{
584-
if (this.network.Consensus.MaxReorgLength != 0)
585-
{
586-
rewindDataWindow = this.network.Consensus.MaxReorgLength + 1;
587-
}
588-
else
589-
{
590-
// TODO: make the rewind data window a configuration
591-
// parameter of evern a network parameter.
592-
593-
// For POW assume BTC where a rewind data of 100 is more then enough.
594-
rewindDataWindow = 100;
595-
}
596-
}
597+
int rewindDataWindow = this.CalculateRewindWindow();
597598

598599
int rewindToRemove = this.blockHash.Height - (int)rewindDataWindow;
599600

@@ -606,6 +607,33 @@ public void SaveChanges(IList<UnspentOutput> outputs, HashHeightPair oldBlockHas
606607
}
607608
}
608609

610+
/// <summary>
611+
/// Calculate the window of how many rewind items to keep in memory.
612+
/// </summary>
613+
/// <returns></returns>
614+
public int CalculateRewindWindow()
615+
{
616+
uint rewindDataWindow = 10;
617+
618+
if (this.blockHash.Height >= this.lastCheckpointHeight)
619+
{
620+
if (this.network.Consensus.MaxReorgLength != 0)
621+
{
622+
rewindDataWindow = this.network.Consensus.MaxReorgLength + 1;
623+
}
624+
else
625+
{
626+
// TODO: make the rewind data window a configuration
627+
// parameter of every a network parameter.
628+
629+
// For POW assume BTC where a rewind data of 100 is more then enough.
630+
rewindDataWindow = 100;
631+
}
632+
}
633+
634+
return (int)rewindDataWindow;
635+
}
636+
609637
public HashHeightPair Rewind()
610638
{
611639
if (this.innerBlockHash == null)

0 commit comments

Comments
 (0)