From 7bf3c0a1237459a36a62b32750d6d307a45d257e Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sat, 11 Jun 2022 22:54:42 +1000 Subject: [PATCH 01/42] Add GetBalance to CoinView --- .../ColdStakingDestinationReader.cs | 1 + .../CoinViews/CachedCoinView.cs | 79 ++++++++- .../CoinViews/CoinView.cs | 3 + .../CoinViews/Coindb/BaseCoindb.cs | 102 +++++++++++ .../CoinViews/Coindb/DBreezeCoindb.cs | 7 +- .../CoinViews/Coindb/ICoindb.cs | 4 +- .../CoinViews/Coindb/IDb.cs | 24 +++ .../CoinViews/Coindb/LevelDb.cs | 159 ++++++++++++++++++ .../CoinViews/Coindb/LeveldbCoindb.cs | 129 ++++++++------ .../CoinViews/Coindb/RocksDbCoindb.cs | 7 +- .../CoinViews/InMemoryCoinView.cs | 10 ++ .../LightWalletFeature.cs | 2 - .../MemPoolCoinView.cs | 5 + .../Interfaces/ScriptDestinationReader.cs | 5 - .../WalletFeature.cs | 3 - .../CoinViewTests.cs | 4 +- .../NodeContext.cs | 5 +- .../Consensus/TestInMemoryCoinView.cs | 5 + src/Stratis.Bitcoin/Base/BaseFeature.cs | 1 + .../Interfaces/IScriptAddressReader.cs | 66 +++++++- 20 files changed, 550 insertions(+), 71 deletions(-) create mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs create mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs create mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs diff --git a/src/Stratis.Bitcoin.Features.ColdStaking/ColdStakingDestinationReader.cs b/src/Stratis.Bitcoin.Features.ColdStaking/ColdStakingDestinationReader.cs index dee9cc9c56..681517bc98 100644 --- a/src/Stratis.Bitcoin.Features.ColdStaking/ColdStakingDestinationReader.cs +++ b/src/Stratis.Bitcoin.Features.ColdStaking/ColdStakingDestinationReader.cs @@ -2,6 +2,7 @@ using NBitcoin; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Wallet.Interfaces; +using Stratis.Bitcoin.Interfaces; namespace Stratis.Bitcoin.Features.ColdStaking { diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index 890cd8d2f2..bd928e35db 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -7,6 +7,7 @@ using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities.Extensions; using TracerAttributes; @@ -111,6 +112,10 @@ public long GetScriptSize /// All access to this object has to be protected by . private readonly Dictionary cachedUtxoItems; + /// Tracks pending balance updates for dirty cache entries. + /// All access to this object has to be protected by . + private readonly Dictionary> cacheBalancesByDestination; + /// Number of items in the cache. /// The getter violates the lock contract on , but the lock here is unnecessary as the is marked as readonly. private int cacheCount => this.cachedUtxoItems.Count; @@ -128,11 +133,13 @@ public long GetScriptSize private readonly IDateTimeProvider dateTimeProvider; private readonly ConsensusSettings consensusSettings; private CachePerformanceSnapshot latestPerformanceSnapShot; + private IScriptAddressReader scriptAddressReader; private int lastCheckpointHeight; private readonly Random random; - public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null) + public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, + StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null) { Guard.NotNull(coindb, nameof(CachedCoinView.coindb)); @@ -146,9 +153,11 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, this.rewindDataIndexCache = rewindDataIndexCache; this.lockobj = new object(); this.cachedUtxoItems = new Dictionary(); + this.cacheBalancesByDestination = new Dictionary>(); this.performanceCounter = new CachePerformanceCounter(this.dateTimeProvider); this.lastCacheFlushTime = this.dateTimeProvider.GetUtcNow(); this.cachedRewindData = new Dictionary(); + this.scriptAddressReader = scriptAddressReader; this.random = new Random(); this.lastCheckpointHeight = this.checkpoints.GetLastCheckpointHeight(); @@ -371,10 +380,11 @@ public void Flush(bool force = true) this.logger.LogDebug("Flushing {0} items.", modify.Count); - this.coindb.SaveChanges(modify, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); + this.coindb.SaveChanges(modify, this.cacheBalancesByDestination, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); // All the cached utxos are now on disk so we can clear the cached entry list. this.cachedUtxoItems.Clear(); + this.cacheBalancesByDestination.Clear(); this.cacheSizeBytes = 0; this.cachedRewindData.Clear(); @@ -466,6 +476,10 @@ public void SaveChanges(IList outputs, HashHeightPair oldBlockHas { // DELETE COINS + // Record the UTXO as having been spent at this height. + if (cacheItem.Coins != null) + this.RecordBalanceChange(cacheItem.Coins.TxOut.ScriptPubKey, -cacheItem.Coins.TxOut.Value, (uint)nextBlockHash.Height); + // In cases of an output spent in the same block // it wont exist in cash or in disk so its safe to remove it if (cacheItem.Coins == null) @@ -514,6 +528,9 @@ public void SaveChanges(IList outputs, HashHeightPair oldBlockHas { // ADD COINS + // Update the balance. + this.RecordBalanceChange(output.Coins.TxOut.ScriptPubKey, output.Coins.TxOut.Value, output.Coins.Height); + if (cacheItem.Coins != null) { // Allow overrides. @@ -595,6 +612,7 @@ public HashHeightPair Rewind(HashHeightPair target = null) // All the cached utxos are now on disk so we can clear the cached entry list. this.cachedUtxoItems.Clear(); + this.cacheBalancesByDestination.Clear(); this.cacheSizeBytes = 0; this.dirtyCacheCount = 0; @@ -651,5 +669,62 @@ private void AddBenchStats(StringBuilder log) this.latestPerformanceSnapShot = snapShot; } + + private void RecordBalanceChange(Script scriptPubKey, long satoshis, uint height) + { + if (scriptPubKey.Length == 0 || satoshis == 0) + return; + + foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) + { + if (!this.cacheBalancesByDestination.TryGetValue(txDestination, out Dictionary value)) + { + value = new Dictionary(); + this.cacheBalancesByDestination[txDestination] = value; + } + + if (!value.TryGetValue(height, out long balance)) + balance = 0; + + balance += satoshis; + + value[height] = balance; + } + } + + public IEnumerable<(uint, long)> GetBalance(TxDestination txDestination) + { + IEnumerable<(uint, long)> CachedBalances() + { + if (this.cacheBalancesByDestination.TryGetValue(txDestination, out Dictionary itemsByHeight)) + { + long balance = 0; + + foreach (uint height in itemsByHeight.Keys.OrderBy(k => k)) + { + balance += itemsByHeight[height]; + yield return (height, balance); + } + } + } + + bool first = true; + foreach ((uint height, long satoshis) in this.coindb.GetBalance(txDestination)) + { + if (first) + { + first = false; + + foreach ((uint height2, long satoshis2) in CachedBalances().Reverse()) + yield return (height2, satoshis2 + satoshis); + } + + yield return (height, satoshis); + } + + if (first) + foreach ((uint height2, long satoshis2) in CachedBalances().Reverse()) + yield return (height2, satoshis2); + } } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index f2b6469361..9ba6609c20 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -28,6 +28,7 @@ public interface ICoinView /// /// Information about the changes between the old block and the new block. An item in this list represents a list of all outputs /// for a specific transaction. If a specific output was spent, the output is null. + /// Balance updates between the old block and the new block. /// Block hash of the current tip of the coinview. /// Block hash of the tip of the coinview after the change is applied. /// List of rewind data items to be persisted. This should only be used when calling . @@ -69,5 +70,7 @@ public interface ICoinView /// /// The height of the block. RewindData GetRewindData(int height); + + IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs new file mode 100644 index 0000000000..9aaf88bc2e --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NBitcoin; +using Stratis.Bitcoin.Interfaces; +using Stratis.Bitcoin.Utilities; + +namespace Stratis.Bitcoin.Features.Consensus.CoinViews +{ + /// + /// A base class for and implementations related to other database types. + /// + public class BaseCoindb + { + protected static readonly byte coinsTable = 1; + protected static readonly byte blockTable = 2; + protected static readonly byte rewindTable = 3; + protected static readonly byte stakeTable = 4; + protected static readonly byte balanceTable = 5; + protected static readonly byte balanceAdjustmentTable = 6; + + /// Access to dBreeze database. + protected IDb leveldb; + + /// Hash of the block which is currently the tip of the coinview. + protected HashHeightPair persistedCoinviewTip; + + private readonly IScriptAddressReader scriptAddressReader; + + private readonly Network network; + + public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) + { + this.network = network; + this.scriptAddressReader = scriptAddressReader; + } + + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + long balance = 0; + { + byte[] row = this.leveldb.Get(balanceTable, txDestination.ToBytes()); + balance = (row == null) ? 0 : BitConverter.ToInt64(row); + } + + foreach ((byte[] key, byte[] value) in this.leveldb.GetAll(balanceAdjustmentTable, ascending: false, lastKey: BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse().ToArray(), includeLastKey: false)) + { + yield return (BitConverter.ToUInt32(key.Reverse().ToArray()), balance); + balance -= BitConverter.ToInt64(value); + } + + yield return (0, balance); + } + + protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates) + { + foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) + { + long totalAdjustment = 0; + + foreach (uint height in balanceAdjustments.Keys.OrderBy(k => k)) + { + var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); + byte[] row = this.leveldb.Get(balanceAdjustmentTable, key); + long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + balanceAdjustments[height]; + batch.Put(balanceAdjustmentTable, key, BitConverter.GetBytes(balance)); + + totalAdjustment += balance; + } + + { + var key = txDestination.ToBytes(); + byte[] row = this.leveldb.Get(balanceTable, key); + long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + totalAdjustment; + batch.Put(balanceTable, key, BitConverter.GetBytes(balance)); + } + } + } + + protected void Update(Dictionary> balanceAdjustments, Script scriptPubKey, uint height, long change) + { + if (scriptPubKey.Length == 0 || change == 0) + return; + + foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) + { + if (!balanceAdjustments.TryGetValue(txDestination, out Dictionary value)) + { + value = new Dictionary(); + balanceAdjustments[txDestination] = value; + } + + if (!value.TryGetValue(height, out long balance)) + balance = change; + else + balance += change; + + value[height] = balance; + } + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs index 638444e332..9ef54229bb 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs @@ -97,6 +97,11 @@ public HashHeightPair GetTipHash() return tipHash; } + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + throw new NotImplementedException(); + } + public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -150,7 +155,7 @@ private void SetBlockHash(DBreeze.Transactions.Transaction transaction, HashHeig transaction.Insert("BlockHash", blockHashKey, nextBlockHash.ToBytes()); } - public void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) + public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { int insertedEntities = 0; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index 32b23b5f49..fc3215998d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -34,7 +34,7 @@ public interface ICoindb /// Block hash of the current tip of the coinview. /// Block hash of the tip of the coinview after the change is applied. /// List of rewind data items to be persisted. - void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null); + void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null); /// /// Obtains information about unspent outputs. @@ -76,6 +76,8 @@ public interface ICoindb /// /// int GetMinRewindHeight(); + + IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } public interface IStakedb : ICoindb diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs new file mode 100644 index 0000000000..6237e51aaf --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace Stratis.Bitcoin.Features.Consensus.CoinViews +{ + public interface IDb : IDisposable + { + byte[] Get(byte table, byte[] key); + + IEnumerable<(byte[], byte[])> GetAll(byte table, bool keysOnly = false, bool ascending = true, + byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true); + + IDbBatch GetWriteBatch(); + } + + public interface IDbBatch : IDisposable + { + IDbBatch Put(byte table, byte[] key, byte[] value); + + IDbBatch Delete(byte table, byte[] key); + + void Write(); + } +} diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs new file mode 100644 index 0000000000..ee51162454 --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LevelDB; +using NBitcoin; + +namespace Stratis.Bitcoin.Features.Consensus.CoinViews +{ + public class LevelDbBatch : WriteBatch, IDbBatch + { + DB db; + + public LevelDbBatch(DB db) + { + this.db = db; + } + + public IDbBatch Put(byte table, byte[] key, byte[] value) + { + return (IDbBatch)base.Put(new[] { table }.Concat(key).ToArray(), value); + } + + public IDbBatch Delete(byte table, byte[] key) + { + return (IDbBatch)base.Delete(new[] { table }.Concat(key).ToArray()); + } + + public void Write() + { + this.db.Write(this, new WriteOptions() { Sync = true }); + } + } + + public class LevelDb : DB, IDb + { + private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + + public LevelDb(Options options, string name) : base(options, name) + { + } + + public IDbBatch GetWriteBatch() => new LevelDbBatch(this); + + public byte[] Get(byte table, byte[] key) + { + return base.Get(new[] { table }.Concat(key).ToArray()); + } + + public IEnumerable<(byte[], byte[])> GetAll(byte keyPrefix, bool keysOnly = false, bool ascending = true, + byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) + { + using (Iterator iterator = this.CreateIterator()) + { + byte[] firstKeyBytes = (firstKey == null) ? null : new[] { keyPrefix }.Concat(firstKey).ToArray(); + byte[] lastKeyBytes = (lastKey == null) ? null : new[] { keyPrefix }.Concat(lastKey).ToArray(); + bool done = false; + Func breakLoop; + Action next; + + if (!ascending) + { + if (lastKeyBytes == null) + { + // If no last key was provided then seek to the last record with this prefix + // by first seeking to the first record with the next prefix... + iterator.Seek(new[] { (byte)(keyPrefix + 1) }); + + // ...then back up to the previous value if the iterator is still valid. + if (iterator.IsValid()) + iterator.Prev(); + else + // If the iterator is invalid then there were no records with greater prefixes. + // In this case we can simply seek to the last record. + iterator.SeekToLast(); + } + else + { + // Seek to the last key if it was provided. + iterator.Seek(lastKeyBytes); + + // If it won't be returned, and is current/found, then move to the previous value. + if (!includeLastKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes)) + iterator.Prev(); + } + + breakLoop = (firstKeyBytes == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, firstKeyBytes); + if (compareResult <= 0) + { + // If this is the first key and its not included or we've overshot the range then stop without yielding a value. + if (!includeFirstKey || compareResult < 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Prev(); + } + else /* Ascending */ + { + if (firstKeyBytes == null) + { + // If no first key was provided then use the key prefix to find the first value. + iterator.Seek(new[] { keyPrefix }); + } + else + { + // Seek to the first key if it was provided. + iterator.Seek(firstKeyBytes); + + // If it won't be returned, and is current/found, then move to the next value. + if (!includeFirstKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), firstKeyBytes)) + iterator.Next(); + } + + breakLoop = (lastKeyBytes == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, lastKeyBytes); + if (compareResult >= 0) + { + // If this is the last key and its not included or we've overshot the range then stop without yielding a value. + if (!includeLastKey || compareResult > 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Next(); + } + + while (iterator.IsValid()) + { + byte[] keyBytes = iterator.Key(); + + if (keyBytes[0] != keyPrefix || (breakLoop != null && breakLoop(keyBytes))) + break; + + yield return (keyBytes.Skip(1).ToArray(), keysOnly ? null : iterator.Value()); + + if (done) + break; + + next(); + } + } + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index 2584bab096..dafc0d1d91 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -7,22 +7,19 @@ using NBitcoin; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.Consensus.CoinViews { - /// - /// Persistent implementation of coinview using the dBreeze database engine. - /// - public class LevelDbCoindb : ICoindb, IStakedb, IDisposable +/// +/// Persistent implementation of coinview using the dBreeze database engine. +/// +public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable { /// Database key under which the block hash of the coin view's current tip is stored. private static readonly byte[] blockHashKey = new byte[0]; - private static readonly byte coinsTable = 1; - private static readonly byte blockTable = 2; - private static readonly byte rewindTable = 3; - private static readonly byte stakeTable = 4; private readonly string dataFolder; @@ -32,29 +29,25 @@ public class LevelDbCoindb : ICoindb, IStakedb, IDisposable /// Specification of the network the node runs on - regtest/testnet/mainnet. private readonly Network network; - /// Hash of the block which is currently the tip of the coinview. - private HashHeightPair persistedCoinviewTip; - /// Performance counter to measure performance of the database insert and query operations. private readonly BackendPerformanceCounter performanceCounter; - private BackendPerformanceSnapshot latestPerformanceSnapShot; + private readonly IScriptAddressReader scriptAddressReader; - /// Access to dBreeze database. - private DB leveldb; + private BackendPerformanceSnapshot latestPerformanceSnapShot; private readonly DBreezeSerializer dBreezeSerializer; private const int MaxRewindBatchSize = 10000; public LevelDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer) - : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer) + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) + : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) { } public LevelDbCoindb(Network network, string dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer) + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : base(network, scriptAddressReader) { Guard.NotNull(network, nameof(network)); Guard.NotEmpty(dataFolder, nameof(dataFolder)); @@ -73,26 +66,26 @@ public void Initialize(ChainedHeader chainTip) { // Open a connection to a new DB and create if not found var options = new Options { CreateIfMissing = true }; - this.leveldb = new DB(options, this.dataFolder); + this.leveldb = new LevelDb(options, this.dataFolder); // Check if key bytes are in the wrong endian order. HashHeightPair current = this.GetTipHash(); if (current != null) { - byte[] row = this.leveldb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(current.Height)).ToArray()); + byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(current.Height)); // Fix the table if required. if (row != null) { // To be sure, check the next height too. - byte[] row2 = (current.Height > 1) ? this.leveldb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(current.Height - 1)).ToArray()) : new byte[] { }; + byte[] row2 = (current.Height > 1) ? this.leveldb.Get(rewindTable, BitConverter.GetBytes(current.Height - 1)) : new byte[] { }; if (row2 != null) { this.logger.LogInformation("Fixing the coin db."); var rows = new Dictionary(); - using (var iterator = this.leveldb.CreateIterator()) + using (var iterator = ((DB)this.leveldb).CreateIterator()) { iterator.Seek(new byte[] { rewindTable }); @@ -111,19 +104,19 @@ public void Initialize(ChainedHeader chainTip) } } - using (var batch = new WriteBatch()) + using (var batch = this.leveldb.GetWriteBatch()) { foreach (int height in rows.Keys.OrderBy(k => k)) { - batch.Delete(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height)).ToArray()); + batch.Delete(rewindTable, BitConverter.GetBytes(height)); } foreach (int height in rows.Keys.OrderBy(k => k)) { - batch.Put(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray(), rows[height]); + batch.Put(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray(), rows[height]); } - this.leveldb.Write(batch, new WriteOptions() { Sync = true }); + batch.Write(); } } } @@ -135,10 +128,10 @@ public void Initialize(ChainedHeader chainTip) if (this.GetTipHash() == null) { - using (var batch = new WriteBatch()) + using (var batch = this.leveldb.GetWriteBatch()) { this.SetBlockHash(batch, new HashHeightPair(genesis.GetHash(), 0)); - this.leveldb.Write(batch, new WriteOptions() { Sync = true }); + batch.Write(); } } @@ -156,7 +149,7 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { heightToCheck += 1; - byte[] row = this.leveldb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(heightToCheck).Reverse()).ToArray()); + byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); if (row == null) break; @@ -173,17 +166,17 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) this.logger.LogInformation("Coin database integrity good."); } - private void SetBlockHash(WriteBatch batch, HashHeightPair nextBlockHash) + private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash) { this.persistedCoinviewTip = nextBlockHash; - batch.Put(new byte[] { blockTable }.Concat(blockHashKey).ToArray(), nextBlockHash.ToBytes()); + batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); } public HashHeightPair GetTipHash() { if (this.persistedCoinviewTip == null) { - var row = this.leveldb.Get(new byte[] { blockTable }.Concat(blockHashKey).ToArray()); + var row = this.leveldb.Get(blockTable, blockHashKey); if (row != null) { this.persistedCoinviewTip = new HashHeightPair(); @@ -204,7 +197,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) foreach (OutPoint outPoint in utxos) { - byte[] row = this.leveldb.Get(new byte[] { coinsTable }.Concat(outPoint.ToBytes()).ToArray()); + byte[] row = this.leveldb.Get(coinsTable, outPoint.ToBytes()); Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); @@ -216,12 +209,14 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) return res; } - public void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) + public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { int insertedEntities = 0; - using (var batch = new WriteBatch()) + using (var batch = this.leveldb.GetWriteBatch()) { + this.AdjustBalance(batch, balanceUpdates); + using (new StopwatchDisposable(o => this.performanceCounter.AddInsertTime(o))) { HashHeightPair current = this.GetTipHash(); @@ -239,7 +234,7 @@ public void SaveChanges(IList unspentOutputs, HashHeightPair oldB if (coin.Coins == null) { this.logger.LogDebug("Outputs of transaction ID '{0}' are prunable and will be removed from the database.", coin.OutPoint); - batch.Delete(new byte[] { coinsTable }.Concat(coin.OutPoint.ToBytes()).ToArray()); + batch.Delete(coinsTable, coin.OutPoint.ToBytes()); } else { @@ -254,7 +249,7 @@ public void SaveChanges(IList unspentOutputs, HashHeightPair oldB var coin = toInsert[i]; this.logger.LogDebug("Outputs of transaction ID '{0}' are NOT PRUNABLE and will be inserted into the database. {1}/{2}.", coin.OutPoint, i, toInsert.Count); - batch.Put(new byte[] { coinsTable }.Concat(coin.OutPoint.ToBytes()).ToArray(), this.dBreezeSerializer.Serialize(coin.Coins)); + batch.Put(coinsTable, coin.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(coin.Coins)); } if (rewindDataList != null) @@ -265,13 +260,13 @@ public void SaveChanges(IList unspentOutputs, HashHeightPair oldB this.logger.LogDebug("Rewind state #{0} created.", nextRewindIndex); - batch.Put(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(nextRewindIndex).Reverse()).ToArray(), this.dBreezeSerializer.Serialize(rewindData)); + batch.Put(rewindTable, BitConverter.GetBytes(nextRewindIndex).Reverse().ToArray(), this.dBreezeSerializer.Serialize(rewindData)); } } insertedEntities += unspentOutputs.Count; this.SetBlockHash(batch, nextBlockHash); - this.leveldb.Write(batch, new WriteOptions() { Sync = true }); + batch.Write(); } } @@ -282,7 +277,7 @@ public void SaveChanges(IList unspentOutputs, HashHeightPair oldB public int GetMinRewindHeight() { // Find the first row with a rewind table key prefix. - using (var iterator = this.leveldb.CreateIterator()) + using (var iterator = ((DB)this.leveldb).CreateIterator()) { iterator.Seek(new byte[] { rewindTable }); if (!iterator.IsValid()) @@ -304,40 +299,72 @@ public HashHeightPair Rewind(HashHeightPair target) return RewindInternal(current.Height, target); } + private bool TryGetCoins(byte[] key, out Coins coins) + { + byte[] row2 = this.leveldb.Get(coinsTable, key); + if (row2 == null) + { + coins = null; + return false; + } + + coins = this.dBreezeSerializer.Deserialize(row2); + + return true; + } + private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { HashHeightPair res = null; - using (var batch = new WriteBatch()) + using (var batch = this.leveldb.GetWriteBatch()) { + var balanceAdjustments = new Dictionary>(); + for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) { - byte[] row = this.leveldb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray()); + byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); + byte[] row = this.leveldb.Get(rewindTable, rowKey); if (row == null) throw new InvalidOperationException($"No rewind data found for block at height {height}."); - batch.Delete(BitConverter.GetBytes(height)); + batch.Delete(rewindTable, rowKey); var rewindData = this.dBreezeSerializer.Deserialize(row); foreach (OutPoint outPoint in rewindData.OutputsToRemove) { - this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - batch.Delete(new byte[] { coinsTable }.Concat(outPoint.ToBytes()).ToArray()); + byte[] key = outPoint.ToBytes(); + if (this.TryGetCoins(key, out Coins coins)) + { + this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); + + Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); + + batch.Delete(coinsTable, key); + } + else + { + throw new InvalidOperationException(string.Format("Outputs of outpoint '{0}' were not found when attempting removal.", outPoint)); + } } foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) { this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); - batch.Put(new byte[] { coinsTable }.Concat(rewindDataOutput.OutPoint.ToBytes()).ToArray(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); + batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); + + Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); } res = rewindData.PreviousBlockHash; } + AdjustBalance(batch, balanceAdjustments); + this.SetBlockHash(batch, res); - this.leveldb.Write(batch, new WriteOptions() { Sync = true }); + batch.Write(); } return res; @@ -345,7 +372,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) public RewindData GetRewindData(int height) { - byte[] row = this.leveldb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray()); + byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); return row != null ? this.dBreezeSerializer.Deserialize(row) : null; } @@ -355,18 +382,18 @@ public RewindData GetRewindData(int height) /// List of POS block information to be examined and persists if unsaved. public void PutStake(IEnumerable stakeEntries) { - using (var batch = new WriteBatch()) + using (var batch = this.leveldb.GetWriteBatch()) { foreach (StakeItem stakeEntry in stakeEntries) { if (!stakeEntry.InStore) { - batch.Put(new byte[] { stakeTable }.Concat(stakeEntry.BlockId.ToBytes(false)).ToArray(), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); + batch.Put(stakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); stakeEntry.InStore = true; } } - this.leveldb.Write(batch, new WriteOptions() { Sync = true }); + batch.Write(); } } @@ -379,7 +406,7 @@ public void GetStake(IEnumerable blocklist) foreach (StakeItem blockStake in blocklist) { this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.leveldb.Get(new byte[] { stakeTable }.Concat(blockStake.BlockId.ToBytes(false)).ToArray()); + byte[] stakeRow = this.leveldb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); if (stakeRow != null) { diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs index 6e55bcd059..251b836ea8 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs @@ -172,6 +172,11 @@ public HashHeightPair GetTipHash() return this.persistedCoinviewTip; } + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + throw new NotImplementedException(); + } + public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -194,7 +199,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) return res; } - public void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) + public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { int insertedEntities = 0; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs index 2f2bd59f15..fedfffe188 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs @@ -43,6 +43,11 @@ public void CacheCoins(OutPoint[] utxos) throw new NotImplementedException(); } + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + throw new NotImplementedException(); + } + /// public FetchCoinsResponse FetchCoins(OutPoint[] txIds) { @@ -95,6 +100,11 @@ public void SaveChanges(IList unspentOutputs, HashHeightPair oldB } } + public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) + { + this.SaveChanges(unspentOutputs, oldBlockHash, nextBlockHash, rewindDataList); + } + public int GetMinRewindHeight() { throw new NotImplementedException(); diff --git a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs index d17e1cf2bf..b126a4be00 100644 --- a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs +++ b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs @@ -13,7 +13,6 @@ using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Connection; -using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.BlockStore.Pruning; using Stratis.Bitcoin.Features.LightWallet.Broadcasting; @@ -229,7 +228,6 @@ public static IFullNodeBuilder UseLightWallet(this IFullNodeBuilder fullNodeBuil services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs index 91f98a282a..380f1bfcb4 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs @@ -74,6 +74,11 @@ public FetchCoinsResponse FetchCoins(OutPoint[] txIds) { throw new NotImplementedException(); } + + public IEnumerable<(uint, long)> GetBalance(TxDestination txDestination) + { + throw new NotImplementedException(); + } public HashHeightPair Rewind(HashHeightPair target) { diff --git a/src/Stratis.Bitcoin.Features.Wallet/Interfaces/ScriptDestinationReader.cs b/src/Stratis.Bitcoin.Features.Wallet/Interfaces/ScriptDestinationReader.cs index bafb1e05f5..7557278fd4 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/Interfaces/ScriptDestinationReader.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/Interfaces/ScriptDestinationReader.cs @@ -6,11 +6,6 @@ namespace Stratis.Bitcoin.Features.Wallet.Interfaces { - public interface IScriptDestinationReader : IScriptAddressReader - { - IEnumerable GetDestinationFromScriptPubKey(Network network, Script script); - } - public class ScriptDestinationReader : IScriptAddressReader { private readonly IScriptAddressReader scriptAddressReader; diff --git a/src/Stratis.Bitcoin.Features.Wallet/WalletFeature.cs b/src/Stratis.Bitcoin.Features.Wallet/WalletFeature.cs index d459f9ffdd..7e6c6bd6be 100644 --- a/src/Stratis.Bitcoin.Features.Wallet/WalletFeature.cs +++ b/src/Stratis.Bitcoin.Features.Wallet/WalletFeature.cs @@ -9,14 +9,12 @@ using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Connection; -using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.MemoryPool; using Stratis.Bitcoin.Features.RPC; using Stratis.Bitcoin.Features.Wallet.Broadcasting; using Stratis.Bitcoin.Features.Wallet.Interfaces; using Stratis.Bitcoin.Features.Wallet.Services; -using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.Wallet @@ -181,7 +179,6 @@ public static IFullNodeBuilder UseWallet(this IFullNodeBuilder fullNodeBuilder) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(new ScriptAddressReader()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs index dc3ae155a1..6a3c7651cb 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs @@ -46,14 +46,14 @@ public void TestDBreezeSerialization() Block genesis = ctx.Network.GetGenesis(); var genesisChainedHeader = new ChainedHeader(genesis.Header, ctx.Network.GenesisHash, 0); ChainedHeader chained = this.MakeNext(genesisChainedHeader, ctx.Network); - ctx.Coindb.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), new Coins(0, genesis.Transactions[0].Outputs.First(), true)) }, new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); + ctx.Coindb.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), new Coins(0, genesis.Transactions[0].Outputs.First(), true)) }, new Dictionary>(), new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); Assert.NotNull(ctx.Coindb.FetchCoins(new[] { new OutPoint(genesis.Transactions[0], 0) }).UnspentOutputs.Values.FirstOrDefault().Coins); Assert.Null(ctx.Coindb.FetchCoins(new[] { new OutPoint() }).UnspentOutputs.Values.FirstOrDefault().Coins); ChainedHeader previous = chained; chained = this.MakeNext(this.MakeNext(genesisChainedHeader, ctx.Network), ctx.Network); chained = this.MakeNext(this.MakeNext(genesisChainedHeader, ctx.Network), ctx.Network); - ctx.Coindb.SaveChanges(new List(), new HashHeightPair(previous), new HashHeightPair(chained)); + ctx.Coindb.SaveChanges(new List(), new Dictionary>(), new HashHeightPair(previous), new HashHeightPair(chained)); Assert.Equal(chained.HashBlock, ctx.Coindb.GetTipHash().Hash); ctx.ReloadPersistentCoinView(chained); Assert.Equal(chained.HashBlock, ctx.Coindb.GetTipHash().Hash); diff --git a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs index 5efc05f99a..ba6dc565f1 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs @@ -5,6 +5,7 @@ using Moq; using NBitcoin; using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Tests.Common; @@ -27,7 +28,7 @@ public NodeContext(object caller, string name, Network network) this.FolderName = TestBase.CreateTestDir(caller, name); var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); - this.Coindb = new LevelDbCoindb(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer); + this.Coindb = new LevelDbCoindb(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer, new ScriptAddressReader()); this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0)); this.cleanList = new List { (IDisposable)this.Coindb }; } @@ -65,7 +66,7 @@ public void ReloadPersistentCoinView(ChainedHeader chainTip) this.cleanList.Remove((IDisposable)this.Coindb); var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); - this.Coindb = new LevelDbCoindb(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer); + this.Coindb = new LevelDbCoindb(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer, new ScriptAddressReader()); this.Coindb.Initialize(chainTip); this.cleanList.Add((IDisposable)this.Coindb); diff --git a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs index 3ebdd093a1..39b69d75b9 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs @@ -63,6 +63,11 @@ public FetchCoinsResponse FetchCoins(OutPoint[] txIds) } } + public IEnumerable<(uint, long)> GetBalance(TxDestination txDestination) + { + throw new NotImplementedException(); + } + /// public void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { diff --git a/src/Stratis.Bitcoin/Base/BaseFeature.cs b/src/Stratis.Bitcoin/Base/BaseFeature.cs index 31f50d4ab6..68d26bcfa7 100644 --- a/src/Stratis.Bitcoin/Base/BaseFeature.cs +++ b/src/Stratis.Bitcoin/Base/BaseFeature.cs @@ -487,6 +487,7 @@ public static IFullNodeBuilder UseBaseFeature(this IFullNodeBuilder fullNodeBuil services.AddSingleton(); // Consensus + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Bitcoin/Interfaces/IScriptAddressReader.cs b/src/Stratis.Bitcoin/Interfaces/IScriptAddressReader.cs index 8a6c3aebe0..9c8b9f6877 100644 --- a/src/Stratis.Bitcoin/Interfaces/IScriptAddressReader.cs +++ b/src/Stratis.Bitcoin/Interfaces/IScriptAddressReader.cs @@ -1,4 +1,7 @@ -using NBitcoin; +using System.Collections.Generic; +using System.Linq; +using NBitcoin; +using NBitcoin.DataEncoders; namespace Stratis.Bitcoin.Interfaces { @@ -15,4 +18,65 @@ public interface IScriptAddressReader /// string GetAddressFromScriptPubKey(Network network, Script script); } + + public interface IScriptDestinationReader : IScriptAddressReader + { + IEnumerable GetDestinationFromScriptPubKey(Network network, Script script); + } + + public static class IScriptAddressReaderExt + { + public static IEnumerable GetDestinationFromScriptPubKey(this IScriptAddressReader scriptAddressReader, Network network, Script redeemScript) + { + ScriptTemplate scriptTemplate = network.StandardScriptsRegistry.GetTemplateFromScriptPubKey(redeemScript); + + if (scriptTemplate != null) + { + // We need scripts suitable for matching to HDAddress.ScriptPubKey. + switch (scriptTemplate.Type) + { + case TxOutType.TX_PUBKEYHASH: + yield return PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript); + break; + case TxOutType.TX_PUBKEY: + yield return PayToPubkeyTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript).Hash; + break; + case TxOutType.TX_SCRIPTHASH: + yield return PayToScriptHashTemplate.Instance.ExtractScriptPubKeyParameters(redeemScript); + break; + case TxOutType.TX_SEGWIT: + TxDestination txDestination = PayToWitTemplate.Instance.ExtractScriptPubKeyParameters(network, redeemScript); + if (txDestination != null) + yield return new KeyId(txDestination.ToBytes()); + break; + default: + if (scriptAddressReader is IScriptDestinationReader scriptDestinationReader) + { + foreach (TxDestination destination in scriptDestinationReader.GetDestinationFromScriptPubKey(network, redeemScript)) + { + yield return destination; + } + } + else + { + TxDestination GetDestinationForAddress(string address) + { + if (address == null) + return null; + + byte[] decoded = Encoders.Base58Check.DecodeData(address); + return new KeyId(new uint160(decoded.Skip(network.GetVersionBytes(Base58Type.PUBKEY_ADDRESS, true).Length).ToArray())); + } + + string address = scriptAddressReader.GetAddressFromScriptPubKey(network, redeemScript); + TxDestination destination = GetDestinationForAddress(address); + if (destination != null) + yield return destination; + } + + break; + } + } + } + } } From 4a3d8f63597a151bd1da5d2f7f91ff58ded02237 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 02:19:26 +1000 Subject: [PATCH 02/42] Fix --- .../CoinViews/Coindb/BaseCoindb.cs | 6 +++++- .../CoinViews/Coindb/LevelDb.cs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs index 9aaf88bc2e..a5cef2af76 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -43,7 +43,11 @@ public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) balance = (row == null) ? 0 : BitConverter.ToInt64(row); } - foreach ((byte[] key, byte[] value) in this.leveldb.GetAll(balanceAdjustmentTable, ascending: false, lastKey: BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse().ToArray(), includeLastKey: false)) + foreach ((byte[] key, byte[] value) in this.leveldb.GetAll(balanceAdjustmentTable, ascending: false, + lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), + includeLastKey: false, + firstKey: txDestination.ToBytes(), + includeFirstKey: false)) { yield return (BitConverter.ToUInt32(key.Reverse().ToArray()), balance); balance -= BitConverter.ToInt64(value); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index ee51162454..26e0fc57ef 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -79,7 +79,9 @@ public byte[] Get(byte table, byte[] key) iterator.Seek(lastKeyBytes); // If it won't be returned, and is current/found, then move to the previous value. - if (!includeLastKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes)) + if (!iterator.IsValid()) + iterator.SeekToLast(); + else if (!(includeLastKey && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes))) iterator.Prev(); } From 8a69fc61c6373807aee5d90f1b20b7c1862b7711 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 02:22:43 +1000 Subject: [PATCH 03/42] Fix indentation --- .../CoinViews/Coindb/LeveldbCoindb.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index dafc0d1d91..d8faf1e4fe 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -12,15 +12,14 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { -/// -/// Persistent implementation of coinview using the dBreeze database engine. -/// -public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable + /// + /// Persistent implementation of coinview using the LevelDb database engine. + /// + public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable { /// Database key under which the block hash of the coin view's current tip is stored. private static readonly byte[] blockHashKey = new byte[0]; - private readonly string dataFolder; /// Instance logger. @@ -32,8 +31,6 @@ public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable /// Performance counter to measure performance of the database insert and query operations. private readonly BackendPerformanceCounter performanceCounter; - private readonly IScriptAddressReader scriptAddressReader; - private BackendPerformanceSnapshot latestPerformanceSnapShot; private readonly DBreezeSerializer dBreezeSerializer; From 230763fa2da7de46af6ddb0cd9ea4f2fd28659be Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 10:28:55 +1000 Subject: [PATCH 04/42] Add XML comment for GetBalance method --- .../CoinViews/Coindb/ICoindb.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index fc3215998d..364e9cdbf6 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -77,6 +77,13 @@ public interface ICoindb /// int GetMinRewindHeight(); + /// + /// Returns a combination of (height, satoshis) values with the cumulative balance up to the corresponding height. + /// + /// The destination value derived from the address being queried. + /// A combination of (height, satoshis) values with the cumulative balance up to the corresponding height. + /// Balance updates(even when nett 0) are delivered for every height at which transactions for the address had been recorded + /// and as such the returned heights can be used in conjunction with the block store to discover all related transactions. IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } From 4d7951ee4c5165d218b80e1738ee91ac02059eb6 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 10:32:29 +1000 Subject: [PATCH 05/42] More XML comment updates --- .../CoinViews/CoinView.cs | 8 +++++++- .../CoinViews/Coindb/ICoindb.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index 9ba6609c20..4d227729f1 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -28,7 +28,6 @@ public interface ICoinView /// /// Information about the changes between the old block and the new block. An item in this list represents a list of all outputs /// for a specific transaction. If a specific output was spent, the output is null. - /// Balance updates between the old block and the new block. /// Block hash of the current tip of the coinview. /// Block hash of the tip of the coinview after the change is applied. /// List of rewind data items to be persisted. This should only be used when calling . @@ -71,6 +70,13 @@ public interface ICoinView /// The height of the block. RewindData GetRewindData(int height); + /// + /// Returns a combination of (height, satoshis) values with the cumulative balance up to the corresponding height. + /// + /// The destination value derived from the address being queried. + /// A combination of (height, satoshis) values with the cumulative balance up to the corresponding height. + /// Balance updates(even when nett 0) are delivered for every height at which transactions for the address had been recorded + /// and as such the returned heights can be used in conjunction with the block store to discover all related transactions. IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index 364e9cdbf6..47b3a4b978 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -31,6 +31,7 @@ public interface ICoindb /// /// Information about the changes between the old block and the new block. An item in this list represents a list of all outputs /// for a specific transaction. If a specific output was spent, the output is null. + /// Non-cumulative balance updates at each height. /// Block hash of the current tip of the coinview. /// Block hash of the tip of the coinview after the change is applied. /// List of rewind data items to be persisted. From c4b642a90b7b659176c29cf412bc81cf33b23376 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 15:15:57 +1000 Subject: [PATCH 06/42] Update RocksDb and add CoinView fast-forward --- .../CoinViews/CoinView.cs | 5 +- .../CoinViews/Coindb/BaseCoindb.cs | 17 +- .../CoinViews/Coindb/ICoindb.cs | 5 +- .../CoinViews/Coindb/IDb.cs | 2 + .../CoinViews/Coindb/LevelDb.cs | 32 +- .../CoinViews/Coindb/LeveldbCoindb.cs | 109 ++----- .../CoinViews/Coindb/RocksDb.cs | 179 +++++++++++ .../CoinViews/Coindb/RocksDbCoindb.cs | 281 +++++++++--------- .../Rules/CommonRules/LoadCoinviewRule.cs | 2 +- .../Rules/PosConsensusRuleEngine.cs | 5 +- .../Rules/PowConsensusRuleEngine.cs | 34 ++- .../TestChainFactory.cs | 6 +- .../PoAConsensusRuleEngine.cs | 5 +- src/Stratis.Bitcoin/Interfaces/IBlockStore.cs | 37 +++ .../PoW/SmartContractMinerTests.cs | 3 +- 15 files changed, 467 insertions(+), 255 deletions(-) create mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index 4d227729f1..c3c477802d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -75,8 +75,9 @@ public interface ICoinView /// /// The destination value derived from the address being queried. /// A combination of (height, satoshis) values with the cumulative balance up to the corresponding height. - /// Balance updates(even when nett 0) are delivered for every height at which transactions for the address had been recorded - /// and as such the returned heights can be used in conjunction with the block store to discover all related transactions. + /// Balance updates (even when nett 0) are delivered for every height at which transactions for the address + /// had been recorded and as such the returned heights can be used in conjunction with the block store to discover + /// all related transactions. IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs index a5cef2af76..120a4561fa 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -20,7 +20,7 @@ public class BaseCoindb protected static readonly byte balanceAdjustmentTable = 6; /// Access to dBreeze database. - protected IDb leveldb; + protected IDb coinDb; /// Hash of the block which is currently the tip of the coinview. protected HashHeightPair persistedCoinviewTip; @@ -39,11 +39,11 @@ public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) { long balance = 0; { - byte[] row = this.leveldb.Get(balanceTable, txDestination.ToBytes()); + byte[] row = this.coinDb.Get(balanceTable, txDestination.ToBytes()); balance = (row == null) ? 0 : BitConverter.ToInt64(row); } - foreach ((byte[] key, byte[] value) in this.leveldb.GetAll(balanceAdjustmentTable, ascending: false, + foreach ((byte[] key, byte[] value) in this.coinDb.GetAll(balanceAdjustmentTable, ascending: false, lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), includeLastKey: false, firstKey: txDestination.ToBytes(), @@ -56,7 +56,10 @@ public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) yield return (0, balance); } - protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates) + /// + /// The 'skipMissing' flag allows us to rewind coind db's that have incomplete balance information. + /// + protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates, bool skipMissing = false) { foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) { @@ -65,7 +68,9 @@ protected void AdjustBalance(IDbBatch batch, Dictionary k)) { var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); - byte[] row = this.leveldb.Get(balanceAdjustmentTable, key); + byte[] row = this.coinDb.Get(balanceAdjustmentTable, key); + if (skipMissing && row == null) + continue; long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + balanceAdjustments[height]; batch.Put(balanceAdjustmentTable, key, BitConverter.GetBytes(balance)); @@ -74,7 +79,7 @@ protected void AdjustBalance(IDbBatch batch, Dictionary /// The destination value derived from the address being queried. /// A combination of (height, satoshis) values with the cumulative balance up to the corresponding height. - /// Balance updates(even when nett 0) are delivered for every height at which transactions for the address had been recorded - /// and as such the returned heights can be used in conjunction with the block store to discover all related transactions. + /// Balance updates (even when nett 0) are delivered for every height at which transactions for the address + /// had been recorded and as such the returned heights can be used in conjunction with the block store to discover + /// all related transactions. IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination); } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index 6237e51aaf..d20363eb2f 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -11,6 +11,8 @@ public interface IDb : IDisposable byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true); IDbBatch GetWriteBatch(); + + void Clear(); } public interface IDbBatch : IDisposable diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index 26e0fc57ef..46fe4d283e 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -17,12 +17,12 @@ public LevelDbBatch(DB db) public IDbBatch Put(byte table, byte[] key, byte[] value) { - return (IDbBatch)base.Put(new[] { table }.Concat(key).ToArray(), value); + return (IDbBatch)Put(new[] { table }.Concat(key).ToArray(), value); } public IDbBatch Delete(byte table, byte[] key) { - return (IDbBatch)base.Delete(new[] { table }.Concat(key).ToArray()); + return (IDbBatch)Delete(new[] { table }.Concat(key).ToArray()); } public void Write() @@ -31,25 +31,38 @@ public void Write() } } - public class LevelDb : DB, IDb + public class LevelDb : IDb { private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); - public LevelDb(Options options, string name) : base(options, name) + private readonly string name; + + DB db; + + public LevelDb(string name) { + this.name = name; + this.db = new DB(new Options() { CreateIfMissing = true }, name); } - public IDbBatch GetWriteBatch() => new LevelDbBatch(this); + public void Clear() + { + this.db.Dispose(); + System.IO.Directory.Delete(this.name, true); + this.db = new DB(new Options() { CreateIfMissing = true }, this.name); + } + + public IDbBatch GetWriteBatch() => new LevelDbBatch(this.db); public byte[] Get(byte table, byte[] key) { - return base.Get(new[] { table }.Concat(key).ToArray()); + return this.db.Get(new[] { table }.Concat(key).ToArray()); } public IEnumerable<(byte[], byte[])> GetAll(byte keyPrefix, bool keysOnly = false, bool ascending = true, byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) { - using (Iterator iterator = this.CreateIterator()) + using (Iterator iterator = this.db.CreateIterator()) { byte[] firstKeyBytes = (firstKey == null) ? null : new[] { keyPrefix }.Concat(firstKey).ToArray(); byte[] lastKeyBytes = (lastKey == null) ? null : new[] { keyPrefix }.Concat(lastKey).ToArray(); @@ -157,5 +170,10 @@ public byte[] Get(byte table, byte[] key) } } } + + public void Dispose() + { + this.db.Dispose(); + } } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index d8faf1e4fe..c0b9ccbbe7 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using LevelDB; using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Configuration; @@ -62,62 +61,7 @@ public LevelDbCoindb(Network network, string dataFolder, IDateTimeProvider dateT public void Initialize(ChainedHeader chainTip) { // Open a connection to a new DB and create if not found - var options = new Options { CreateIfMissing = true }; - this.leveldb = new LevelDb(options, this.dataFolder); - - // Check if key bytes are in the wrong endian order. - HashHeightPair current = this.GetTipHash(); - - if (current != null) - { - byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(current.Height)); - // Fix the table if required. - if (row != null) - { - // To be sure, check the next height too. - byte[] row2 = (current.Height > 1) ? this.leveldb.Get(rewindTable, BitConverter.GetBytes(current.Height - 1)) : new byte[] { }; - if (row2 != null) - { - this.logger.LogInformation("Fixing the coin db."); - - var rows = new Dictionary(); - - using (var iterator = ((DB)this.leveldb).CreateIterator()) - { - iterator.Seek(new byte[] { rewindTable }); - - while (iterator.IsValid()) - { - byte[] key = iterator.Key(); - - if (key.Length != 5 || key[0] != rewindTable) - break; - - int height = BitConverter.ToInt32(key, 1); - - rows[height] = iterator.Value(); - - iterator.Next(); - } - } - - using (var batch = this.leveldb.GetWriteBatch()) - { - foreach (int height in rows.Keys.OrderBy(k => k)) - { - batch.Delete(rewindTable, BitConverter.GetBytes(height)); - } - - foreach (int height in rows.Keys.OrderBy(k => k)) - { - batch.Put(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray(), rows[height]); - } - - batch.Write(); - } - } - } - } + this.coinDb = new LevelDb(this.dataFolder); EnsureCoinDatabaseIntegrity(chainTip); @@ -125,7 +69,7 @@ public void Initialize(ChainedHeader chainTip) if (this.GetTipHash() == null) { - using (var batch = this.leveldb.GetWriteBatch()) + using (var batch = this.coinDb.GetWriteBatch()) { this.SetBlockHash(batch, new HashHeightPair(genesis.GetHash(), 0)); batch.Write(); @@ -139,6 +83,14 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { this.logger.LogInformation("Checking coin database integrity..."); + // If the balance table is empty then rebuild the coin db. + if (!this.coinDb.GetAll(balanceTable).Any()) + { + this.logger.LogInformation($"Rebuilding coin database to include balance information."); + this.coinDb.Clear(); + return; + } + var heightToCheck = chainTip.Height; // Find the height up to where rewind data is stored above chain tip. @@ -146,7 +98,7 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { heightToCheck += 1; - byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); if (row == null) break; @@ -173,7 +125,7 @@ public HashHeightPair GetTipHash() { if (this.persistedCoinviewTip == null) { - var row = this.leveldb.Get(blockTable, blockHashKey); + var row = this.coinDb.Get(blockTable, blockHashKey); if (row != null) { this.persistedCoinviewTip = new HashHeightPair(); @@ -194,7 +146,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) foreach (OutPoint outPoint in utxos) { - byte[] row = this.leveldb.Get(coinsTable, outPoint.ToBytes()); + byte[] row = this.coinDb.Get(coinsTable, outPoint.ToBytes()); Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); @@ -210,7 +162,7 @@ public void SaveChanges(IList unspentOutputs, Dictionary unspentOutputs, Dictionary public int GetMinRewindHeight() { // Find the first row with a rewind table key prefix. - using (var iterator = ((DB)this.leveldb).CreateIterator()) - { - iterator.Seek(new byte[] { rewindTable }); - if (!iterator.IsValid()) - return -1; - - byte[] key = iterator.Key(); + var res = this.coinDb.GetAll(rewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); + if (res == default || res.Item1.Length != 5) + return -1; - if (key.Length != 5 || key[0] != rewindTable) - return -1; - - return BitConverter.ToInt32(key.SafeSubarray(1, 4).Reverse().ToArray()); - } + return BitConverter.ToInt32(res.Item1.SafeSubarray(0, 4).Reverse().ToArray()); } /// @@ -298,7 +241,7 @@ public HashHeightPair Rewind(HashHeightPair target) private bool TryGetCoins(byte[] key, out Coins coins) { - byte[] row2 = this.leveldb.Get(coinsTable, key); + byte[] row2 = this.coinDb.Get(coinsTable, key); if (row2 == null) { coins = null; @@ -314,14 +257,14 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { HashHeightPair res = null; - using (var batch = this.leveldb.GetWriteBatch()) + using (var batch = this.coinDb.GetWriteBatch()) { var balanceAdjustments = new Dictionary>(); for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) { byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); - byte[] row = this.leveldb.Get(rewindTable, rowKey); + byte[] row = this.coinDb.Get(rewindTable, rowKey); if (row == null) throw new InvalidOperationException($"No rewind data found for block at height {height}."); @@ -358,7 +301,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) res = rewindData.PreviousBlockHash; } - AdjustBalance(batch, balanceAdjustments); + AdjustBalance(batch, balanceAdjustments, true); this.SetBlockHash(batch, res); batch.Write(); @@ -369,7 +312,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) public RewindData GetRewindData(int height) { - byte[] row = this.leveldb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); return row != null ? this.dBreezeSerializer.Deserialize(row) : null; } @@ -379,7 +322,7 @@ public RewindData GetRewindData(int height) /// List of POS block information to be examined and persists if unsaved. public void PutStake(IEnumerable stakeEntries) { - using (var batch = this.leveldb.GetWriteBatch()) + using (var batch = this.coinDb.GetWriteBatch()) { foreach (StakeItem stakeEntry in stakeEntries) { @@ -403,7 +346,7 @@ public void GetStake(IEnumerable blocklist) foreach (StakeItem blockStake in blocklist) { this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.leveldb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); + byte[] stakeRow = this.coinDb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); if (stakeRow != null) { @@ -430,7 +373,7 @@ private void AddBenchStats(StringBuilder log) /// public void Dispose() { - this.leveldb.Dispose(); + this.coinDb.Dispose(); } } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs new file mode 100644 index 0000000000..0b37d34dda --- /dev/null +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using RocksDbSharp; +using NBitcoin; + +namespace Stratis.Bitcoin.Features.Consensus.CoinViews +{ + public class RocksDbBatch : WriteBatch, IDbBatch + { + RocksDbSharp.RocksDb db; + + public RocksDbBatch(RocksDbSharp.RocksDb db) + { + this.db = db; + } + + public IDbBatch Put(byte table, byte[] key, byte[] value) + { + return (IDbBatch)Put(new[] { table }.Concat(key).ToArray(), value); + } + + public IDbBatch Delete(byte table, byte[] key) + { + return (IDbBatch)Delete(new[] { table }.Concat(key).ToArray()); + } + + public void Write() + { + this.db.Write(this); + } + } + + public class RocksDb : IDb + { + private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + + private readonly string name; + + RocksDbSharp.RocksDb db; + + public RocksDb(string name) + { + this.name = name; + this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), name); + } + + public void Clear() + { + this.db.Dispose(); + System.IO.Directory.Delete(this.name, true); + this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), this.name); + } + + public IDbBatch GetWriteBatch() => new RocksDbBatch(this.db); + + public byte[] Get(byte table, byte[] key) + { + return this.db.Get(new[] { table }.Concat(key).ToArray()); + } + + public IEnumerable<(byte[], byte[])> GetAll(byte keyPrefix, bool keysOnly = false, bool ascending = true, + byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) + { + using (Iterator iterator = this.db.NewIterator()) + { + byte[] firstKeyBytes = (firstKey == null) ? null : new[] { keyPrefix }.Concat(firstKey).ToArray(); + byte[] lastKeyBytes = (lastKey == null) ? null : new[] { keyPrefix }.Concat(lastKey).ToArray(); + bool done = false; + Func breakLoop; + Action next; + + if (!ascending) + { + if (lastKeyBytes == null) + { + // If no last key was provided then seek to the last record with this prefix + // by first seeking to the first record with the next prefix... + iterator.Seek(new[] { (byte)(keyPrefix + 1) }); + + // ...then back up to the previous value if the iterator is still valid. + if (iterator.Valid()) + iterator.Prev(); + else + // If the iterator is invalid then there were no records with greater prefixes. + // In this case we can simply seek to the last record. + iterator.SeekToLast(); + } + else + { + // Seek to the last key if it was provided. + iterator.Seek(lastKeyBytes); + + // If it won't be returned, and is current/found, then move to the previous value. + if (!iterator.Valid()) + iterator.SeekToLast(); + else if (!(includeLastKey && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes))) + iterator.Prev(); + } + + breakLoop = (firstKeyBytes == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, firstKeyBytes); + if (compareResult <= 0) + { + // If this is the first key and its not included or we've overshot the range then stop without yielding a value. + if (!includeFirstKey || compareResult < 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Prev(); + } + else /* Ascending */ + { + if (firstKeyBytes == null) + { + // If no first key was provided then use the key prefix to find the first value. + iterator.Seek(new[] { keyPrefix }); + } + else + { + // Seek to the first key if it was provided. + iterator.Seek(firstKeyBytes); + + // If it won't be returned, and is current/found, then move to the next value. + if (!includeFirstKey && iterator.Valid() && byteArrayComparer.Equals(iterator.Key(), firstKeyBytes)) + iterator.Next(); + } + + breakLoop = (lastKeyBytes == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, lastKeyBytes); + if (compareResult >= 0) + { + // If this is the last key and its not included or we've overshot the range then stop without yielding a value. + if (!includeLastKey || compareResult > 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Next(); + } + + while (iterator.Valid()) + { + byte[] keyBytes = iterator.Key(); + + if (keyBytes[0] != keyPrefix || (breakLoop != null && breakLoop(keyBytes))) + break; + + yield return (keyBytes.Skip(1).ToArray(), keysOnly ? null : iterator.Value()); + + if (done) + break; + + next(); + } + } + } + + public void Dispose() + { + this.db.Dispose(); + } + } +} diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs index 251b836ea8..2f07ae7572 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs @@ -4,46 +4,51 @@ using System.Text; using Microsoft.Extensions.Logging; using NBitcoin; -using RocksDbSharp; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.Consensus.CoinViews { /// - /// Persistent implementation of coinview using dBreeze database. + /// Persistent implementation of coinview using the RocksDb database engine. /// - public class RocksDbCoindb : ICoindb, IStakedb, IDisposable + public class RocksDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable { /// Database key under which the block hash of the coin view's current tip is stored. private static readonly byte[] blockHashKey = new byte[0]; - private static readonly byte coinsTable = 1; - private static readonly byte blockTable = 2; - private static readonly byte rewindTable = 3; - private static readonly byte stakeTable = 4; - private readonly string dataFolder; - /// Hash of the block which is currently the tip of the coinview. - private HashHeightPair persistedCoinviewTip; - private readonly DBreezeSerializer dBreezeSerializer; - private DbOptions dbOptions; - private RocksDb rocksDb; - private BackendPerformanceSnapshot latestPerformanceSnapShot; + /// Instance logger. private readonly ILogger logger; + + /// Specification of the network the node runs on - regtest/testnet/mainnet. private readonly Network network; + + /// Performance counter to measure performance of the database insert and query operations. private readonly BackendPerformanceCounter performanceCounter; - public RocksDbCoindb( - Network network, - DataFolder dataFolder, - IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, - DBreezeSerializer dBreezeSerializer) + private BackendPerformanceSnapshot latestPerformanceSnapShot; + + private readonly DBreezeSerializer dBreezeSerializer; + + private const int MaxRewindBatchSize = 10000; + + public RocksDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) + : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) { - this.dataFolder = dataFolder.CoindbPath; + } + + public RocksDbCoindb(Network network, string dataFolder, IDateTimeProvider dateTimeProvider, + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : base(network, scriptAddressReader) + { + Guard.NotNull(network, nameof(network)); + Guard.NotEmpty(dataFolder, nameof(dataFolder)); + + this.dataFolder = dataFolder; this.dBreezeSerializer = dBreezeSerializer; this.logger = LogManager.GetCurrentClassLogger(); this.network = network; @@ -55,69 +60,21 @@ public RocksDbCoindb( public void Initialize(ChainedHeader chainTip) { - this.dbOptions = new DbOptions().SetCreateIfMissing(true); - this.rocksDb = RocksDb.Open(this.dbOptions, this.dataFolder); - - // Check if key bytes are in the wrong endian order. - HashHeightPair current = this.GetTipHash(); - if (current != null) - { - byte[] row = this.rocksDb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(current.Height)).ToArray()); - - // Fix the table if required. - if (row != null) - { - // To be sure, check the next height too. - byte[] row2 = (current.Height > 1) ? this.rocksDb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(current.Height - 1)).ToArray()) : new byte[] { }; - if (row2 != null) - { - this.logger.LogInformation("Fixing the coin db."); - - var rows = new Dictionary(); - - using (var iterator = this.rocksDb.NewIterator()) - { - iterator.Seek(new byte[] { rewindTable }); - - while (iterator.Valid()) - { - byte[] key = iterator.Key(); - - if (key.Length != 5 || key[0] != rewindTable) - break; - - int height = BitConverter.ToInt32(key, 1); - - rows[height] = iterator.Value(); - - iterator.Next(); - } - } - - using (var batch = new WriteBatch()) - { - foreach (int height in rows.Keys.OrderBy(k => k)) - { - batch.Delete(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height)).ToArray()); - } - - foreach (int height in rows.Keys.OrderBy(k => k)) - { - batch.Put(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray(), rows[height]); - } - - this.rocksDb.Write(batch); - } - } - } - } + // Open a connection to a new DB and create if not found + this.coinDb = new RocksDb(this.dataFolder); EnsureCoinDatabaseIntegrity(chainTip); Block genesis = this.network.GetGenesis(); if (this.GetTipHash() == null) - this.SetBlockHash(new HashHeightPair(genesis.GetHash(), 0)); + { + using (var batch = this.coinDb.GetWriteBatch()) + { + this.SetBlockHash(batch, new HashHeightPair(genesis.GetHash(), 0)); + batch.Write(); + } + } this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } @@ -126,6 +83,14 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { this.logger.LogInformation("Checking coin database integrity..."); + // If the balance table is empty then rebuild the coin db. + if (!this.coinDb.GetAll(balanceTable).Any()) + { + this.logger.LogInformation($"Rebuilding coin database to include balance information."); + this.coinDb.Clear(); + return; + } + var heightToCheck = chainTip.Height; // Find the height up to where rewind data is stored above chain tip. @@ -133,35 +98,34 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { heightToCheck += 1; - byte[] row = this.rocksDb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(heightToCheck).Reverse()).ToArray()); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); if (row == null) break; } while (true); - using (var batch = new WriteBatch()) + for (int height = heightToCheck - 1; height > chainTip.Height;) { - for (int height = heightToCheck - 1; height > chainTip.Height; height--) - { - this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); - RewindInternal(batch, height); - } + this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); + + // Do a batch of rewinding. + height = RewindInternal(height, new HashHeightPair(chainTip)).Height; } this.logger.LogInformation("Coin database integrity good."); } - private void SetBlockHash(HashHeightPair nextBlockHash) + private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash) { this.persistedCoinviewTip = nextBlockHash; - this.rocksDb.Put(new byte[] { blockTable }.Concat(blockHashKey).ToArray(), nextBlockHash.ToBytes()); + batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); } public HashHeightPair GetTipHash() { if (this.persistedCoinviewTip == null) { - var row = this.rocksDb.Get(new byte[] { blockTable }.Concat(blockHashKey).ToArray()); + var row = this.coinDb.Get(blockTable, blockHashKey); if (row != null) { this.persistedCoinviewTip = new HashHeightPair(); @@ -172,11 +136,6 @@ public HashHeightPair GetTipHash() return this.persistedCoinviewTip; } - public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) - { - throw new NotImplementedException(); - } - public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -187,7 +146,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) foreach (OutPoint outPoint in utxos) { - byte[] row = this.rocksDb.Get(new byte[] { coinsTable }.Concat(outPoint.ToBytes()).ToArray()); + byte[] row = this.coinDb.Get(coinsTable, outPoint.ToBytes()); Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); @@ -203,14 +162,16 @@ public void SaveChanges(IList unspentOutputs, Dictionary this.performanceCounter.AddInsertTime(o))) { HashHeightPair current = this.GetTipHash(); if (current != oldBlockHash) { - this.logger.LogError("(-)[BLOCKHASH_MISMATCH]"); + this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); throw new InvalidOperationException("Invalid oldBlockHash"); } @@ -222,7 +183,7 @@ public void SaveChanges(IList unspentOutputs, Dictionary unspentOutputs, Dictionary unspentOutputs, Dictionary public int GetMinRewindHeight() { // Find the first row with a rewind table key prefix. - using (var iterator = this.rocksDb.NewIterator()) - { - iterator.Seek(new byte[] { rewindTable }); - if (!iterator.Valid()) - return -1; - - byte[] key = iterator.Key(); + var res = this.coinDb.GetAll(rewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); + if (res == default || res.Item1.Length != 5) + return -1; - if (key.Length != 5 || key[0] != rewindTable) - return -1; - - return BitConverter.ToInt32(key.SafeSubarray(1, 4).Reverse().ToArray()); - } + return BitConverter.ToInt32(res.Item1.SafeSubarray(0, 4).Reverse().ToArray()); } /// public HashHeightPair Rewind(HashHeightPair target) { - using (var batch = new WriteBatch()) + HashHeightPair current = this.GetTipHash(); + return RewindInternal(current.Height, target); + } + + private bool TryGetCoins(byte[] key, out Coins coins) + { + byte[] row2 = this.coinDb.Get(coinsTable, key); + if (row2 == null) { - HashHeightPair current = this.GetTipHash(); - return RewindInternal(batch, current.Height); + coins = null; + return false; } + + coins = this.dBreezeSerializer.Deserialize(row2); + + return true; } - private HashHeightPair RewindInternal(WriteBatch batch, int height) + private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { - byte[] row = this.rocksDb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray()); + HashHeightPair res = null; - if (row == null) - throw new InvalidOperationException($"No rewind data found for block at height {height}."); + using (var batch = this.coinDb.GetWriteBatch()) + { + var balanceAdjustments = new Dictionary>(); - batch.Delete(BitConverter.GetBytes(height)); + for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) + { + byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); + byte[] row = this.coinDb.Get(rewindTable, rowKey); - var rewindData = this.dBreezeSerializer.Deserialize(row); + if (row == null) + throw new InvalidOperationException($"No rewind data found for block at height {height}."); - foreach (OutPoint outPoint in rewindData.OutputsToRemove) - { - this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - batch.Delete(new byte[] { coinsTable }.Concat(outPoint.ToBytes()).ToArray()); - } + batch.Delete(rewindTable, rowKey); - foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) - { - this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); - batch.Put(new byte[] { coinsTable }.Concat(rewindDataOutput.OutPoint.ToBytes()).ToArray(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - } + var rewindData = this.dBreezeSerializer.Deserialize(row); + + foreach (OutPoint outPoint in rewindData.OutputsToRemove) + { + byte[] key = outPoint.ToBytes(); + if (this.TryGetCoins(key, out Coins coins)) + { + this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); + + Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); + + batch.Delete(coinsTable, key); + } + else + { + throw new InvalidOperationException(string.Format("Outputs of outpoint '{0}' were not found when attempting removal.", outPoint)); + } + } + + foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) + { + this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); + batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - this.rocksDb.Write(batch); + Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); + } - this.SetBlockHash(rewindData.PreviousBlockHash); + res = rewindData.PreviousBlockHash; + } - return rewindData.PreviousBlockHash; + AdjustBalance(batch, balanceAdjustments, true); + + this.SetBlockHash(batch, res); + batch.Write(); + } + + return res; } public RewindData GetRewindData(int height) { - byte[] row = this.rocksDb.Get(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray()); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); return row != null ? this.dBreezeSerializer.Deserialize(row) : null; } @@ -334,18 +322,18 @@ public RewindData GetRewindData(int height) /// List of POS block information to be examined and persists if unsaved. public void PutStake(IEnumerable stakeEntries) { - using var batch = new WriteBatch(); + using (var batch = this.coinDb.GetWriteBatch()) { foreach (StakeItem stakeEntry in stakeEntries) { if (!stakeEntry.InStore) { - batch.Put(new byte[] { stakeTable }.Concat(stakeEntry.BlockId.ToBytes(false)).ToArray(), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); + batch.Put(stakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); stakeEntry.InStore = true; } } - this.rocksDb.Write(batch); + batch.Write(); } } @@ -357,8 +345,8 @@ public void GetStake(IEnumerable blocklist) { foreach (StakeItem blockStake in blocklist) { - this.logger.LogDebug("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.rocksDb.Get(new byte[] { stakeTable }.Concat(blockStake.BlockId.ToBytes(false)).ToArray()); + this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); + byte[] stakeRow = this.coinDb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); if (stakeRow != null) { @@ -370,7 +358,7 @@ public void GetStake(IEnumerable blocklist) private void AddBenchStats(StringBuilder log) { - log.AppendLine("======RocksDb Bench======"); + log.AppendLine(">> RocksDb Bench"); BackendPerformanceSnapshot snapShot = this.performanceCounter.Snapshot(); @@ -382,9 +370,10 @@ private void AddBenchStats(StringBuilder log) this.latestPerformanceSnapShot = snapShot; } + /// public void Dispose() { - this.rocksDb.Dispose(); + this.coinDb.Dispose(); } } } \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs index c03e4b0f00..670ab47c2c 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs @@ -66,7 +66,7 @@ public override Task RunAsync(RuleContext context) { // Check that the current block has not been reorged. // Catching a reorg at this point will not require a rewind. - if (context.ValidationContext.BlockToValidate.Header.HashPrevBlock != this.Parent.ChainState.ConsensusTip.HashBlock) + if (context.ValidationContext.BlockToValidate.Header.HashPrevBlock != this.PowParent.UtxoSet.GetTipHash().Hash) { this.Logger.LogDebug("Reorganization detected."); ConsensusErrors.InvalidPrevTip.Throw(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs index 69b224cc04..0db483a139 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs @@ -9,6 +9,7 @@ using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.Interfaces; using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using TracerAttributes; @@ -32,8 +33,8 @@ public class PosConsensusRuleEngine : PowConsensusRuleEngine public PosConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IStakeChain stakeChain, IStakeValidator stakeValidator, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer) - : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore) + : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) { this.StakeChain = stakeChain; this.StakeValidator = stakeValidator; diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs index 97ade69440..c0b1266059 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NBitcoin; @@ -9,6 +10,8 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.CoinViews; +using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using TracerAttributes; @@ -26,16 +29,21 @@ public class PowConsensusRuleEngine : ConsensusRuleEngine public ICoinView UtxoSet { get; } private readonly CoinviewPrefetcher prefetcher; + private readonly IBlockStore blockStore; + private readonly ConsensusRulesContainer consensusRulesContainer; public PowConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore) : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, chainState, invalidBlockHashStore, nodeStats, consensusRulesContainer) { this.logger = loggerFactory.CreateLogger(this.GetType().FullName); + this.consensusRulesContainer = consensusRulesContainer; + this.UtxoSet = utxoSet; this.prefetcher = new CoinviewPrefetcher(this.UtxoSet, chainIndexer, loggerFactory, asyncProvider, checkpoints); + this.blockStore = blockStore; } /// @@ -87,6 +95,30 @@ public override void Initialize(ChainedHeader chainTip) coinViewTip = coinDatabase.Rewind(new HashHeightPair(chainTip)); } + // If the coin view is behind the block store then catch up from the block store. + if (coinViewTip.Height < chainTip.Height) + { + foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(this.ChainIndexer[0], this.ChainIndexer)) + { + if (block == null) + break; + + if ((chainedHeader.Height % 10000) == 0) + this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); + + var ruleContext = new PosRuleContext() + { + ValidationContext = new ValidationContext() { ChainedHeaderToValidate = chainedHeader, BlockToValidate = block }, + SkipValidation = true + }; + + foreach (var rule in this.consensusRulesContainer.FullValidationRules) + { + rule.RunAsync(ruleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } + } + this.logger.LogInformation("Coin view initialized at '{0}'.", coinDatabase.GetTipHash()); } diff --git a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs index b588aeaf41..086b2c3a60 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs @@ -117,7 +117,8 @@ public static async Task CreatePosAsync(Network network, Scri var stakeChain = new StakeChainStore(network, chain, null, loggerFactory); ConsensusRuleEngine consensusRules = new PosConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), inMemoryCoinView, stakeChain, new StakeValidator(network, stakeChain, chain, inMemoryCoinView, loggerFactory), chainState, new InvalidBlockHashStore(dateTimeProvider), - new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), new RewindDataIndexCache(dateTimeProvider, network, finalizedBlockInfoRepository, new Checkpoints()), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); + new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), new RewindDataIndexCache(dateTimeProvider, network, + finalizedBlockInfoRepository, new Checkpoints()), asyncProvider, consensusRulesContainer, new Mock().Object).SetupRulesEngineParent(); IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); @@ -236,7 +237,8 @@ public static async Task CreateAsync(Network network, Script var deployments = new NodeDeployments(network, chain); ConsensusRuleEngine consensusRules = new PowConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), - inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); + inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), + asyncProvider, consensusRulesContainer, new Mock().Object).SetupRulesEngineParent(); IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs index bafaffb8f5..ba108d5aad 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs @@ -9,6 +9,7 @@ using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.Rules; using Stratis.Bitcoin.Features.PoA.Voting; +using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.PoA @@ -30,8 +31,8 @@ public PoAConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDa NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, ISlotsManager slotsManager, PoABlockHeaderValidator poaHeaderValidator, VotingManager votingManager, IFederationManager federationManager, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, - IFederationHistory federationHistory) - : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer) + IFederationHistory federationHistory, IBlockStore blockStore) + : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) { this.SlotsManager = slotsManager; this.PoaHeaderValidator = poaHeaderValidator; diff --git a/src/Stratis.Bitcoin/Interfaces/IBlockStore.cs b/src/Stratis.Bitcoin/Interfaces/IBlockStore.cs index 6f1bc4c238..5261b15091 100644 --- a/src/Stratis.Bitcoin/Interfaces/IBlockStore.cs +++ b/src/Stratis.Bitcoin/Interfaces/IBlockStore.cs @@ -38,4 +38,41 @@ public interface IBlockStore : IDisposable List GetBlocks(List blockHashes); } + + + public static class IBlockStoreExt + { + public static IEnumerable<(ChainedHeader, Block)> BatchBlocksFrom(this IBlockStore blockStore, ChainedHeader previousBlock, ChainIndexer chainIndexer, CancellationTokenSource cancellationToken = null, int batchSize = 100) + { + for (int height = previousBlock.Height + 1; !(cancellationToken?.IsCancellationRequested ?? false);) + { + var hashes = new List(); + for (int i = 0; i < batchSize; i++) + { + ChainedHeader header = chainIndexer.GetHeader(height + i); + if (header == null) + break; + + if (header.Previous != previousBlock) + break; + + hashes.Add(header.HashBlock); + + previousBlock = header; + } + + if (hashes.Count == 0) + yield break; + + List blocks = blockStore.GetBlocks(hashes); + + var buffer = new List<(ChainedHeader, Block)>(); + for (int i = 0; i < blocks.Count && !(cancellationToken?.IsCancellationRequested ?? false); height++, i++) + { + ChainedHeader header = chainIndexer.GetHeader(height); + yield return ((header, blocks[i])); + } + } + } + } } diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs index 7794cce1b3..464e704a9f 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs @@ -257,7 +257,8 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), asyncProvider, - consensusRulesContainer) + consensusRulesContainer, + new Mock().Object) .SetupRulesEngineParent(); var finalizedBlockInfoRepository = new FinalizedBlockInfoRepository(new HashHeightPair()); From fbf4a9bd47c15c04e59c1456adf1bdf47bdde9bf Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 18:43:32 +1000 Subject: [PATCH 07/42] Refactor --- .../ColdStakingControllerTest.cs | 2 +- .../CoinViews/CoinviewTests.cs | 2 +- .../TestChainFactory.cs | 2 +- .../CoinViews/CachedCoinView.cs | 64 +++++++++++++++++-- .../CoinViews/CoinView.cs | 9 +++ .../CoinViews/Coindb/BaseCoindb.cs | 4 +- .../CoinViews/Coindb/LeveldbCoindb.cs | 2 +- .../CoinViews/Coindb/RocksDbCoindb.cs | 2 +- .../CoinViews/InMemoryCoinView.cs | 7 ++ .../Rules/PosConsensusRuleEngine.cs | 2 +- .../Rules/PowConsensusRuleEngine.cs | 50 +-------------- .../MemPoolCoinView.cs | 6 ++ .../PoAConsensusRuleEngine.cs | 2 +- .../CoinViewTests.cs | 11 ++-- .../Consensus/TestInMemoryCoinView.cs | 7 ++ .../PoW/SmartContractMinerTests.cs | 3 +- 16 files changed, 104 insertions(+), 71 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs b/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs index 8709fa2ab7..21795f9678 100644 --- a/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs +++ b/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs @@ -167,7 +167,7 @@ private void CreateMempoolManager() ConsensusRuleEngine consensusRuleEngine = new PosConsensusRuleEngine(this.Network, this.loggerFactory, this.dateTimeProvider, this.chainIndexer, this.nodeDeployments, this.consensusSettings, checkpoints.Object, this.coinView.Object, this.stakeChain.Object, - this.stakeValidator.Object, chainState, new InvalidBlockHashStore(this.dateTimeProvider), new Mock().Object, new Mock().Object, this.asyncProvider, consensusRulesContainer) + this.stakeValidator.Object, chainState, new InvalidBlockHashStore(this.dateTimeProvider), new Mock().Object, new Mock().Object, this.asyncProvider, consensusRulesContainer, null) .SetupRulesEngineParent(); // Create mempool validator. diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs index 8f882403a4..e39ec6b8b0 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs @@ -51,7 +51,7 @@ public CoinviewTests() this.rewindDataIndexCache = new RewindDataIndexCache(this.dateTimeProvider, this.network, new FinalizedBlockInfoRepository(new HashHeightPair()), new Checkpoints()); - this.cachedCoinView = new CachedCoinView(this.network, new Checkpoints(), this.coindb, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new ConsensusSettings(new NodeSettings(this.network)), this.stakeChainStore, this.rewindDataIndexCache); + this.cachedCoinView = new CachedCoinView(this.network, this.coindb, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new ConsensusSettings(new NodeSettings(this.network)), this.stakeChainStore, this.rewindDataIndexCache); this.rewindDataIndexCache.Initialize(this.chainIndexer.Height, this.cachedCoinView); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs index cd152c776e..b81a583a44 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs @@ -123,7 +123,7 @@ public static async Task CreateAsync(Network network, string d testChainContext.InitialBlockDownloadState = new InitialBlockDownloadState(testChainContext.ChainState, testChainContext.Network, consensusSettings, new Checkpoints(), testChainContext.DateTimeProvider); var inMemoryCoinView = new InMemoryCoinView(new HashHeightPair(testChainContext.ChainIndexer.Tip)); - var cachedCoinView = new CachedCoinView(network, new Checkpoints(), inMemoryCoinView, DateTimeProvider.Default, testChainContext.LoggerFactory, new NodeStats(testChainContext.DateTimeProvider, testChainContext.NodeSettings, new Mock().Object), new ConsensusSettings(testChainContext.NodeSettings)); + var cachedCoinView = new CachedCoinView(network, inMemoryCoinView, DateTimeProvider.Default, testChainContext.LoggerFactory, new NodeStats(testChainContext.DateTimeProvider, testChainContext.NodeSettings, new Mock().Object), new ConsensusSettings(testChainContext.NodeSettings)); var dataFolder = new DataFolder(TestBase.AssureEmptyDir(dataDir).FullName); testChainContext.PeerAddressManager = diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index bd928e35db..64bf3dada7 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -6,6 +6,7 @@ using NBitcoin; using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Consensus; +using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; @@ -129,28 +130,27 @@ public long GetScriptSize private long rewindDataSizeBytes; private DateTime lastCacheFlushTime; private readonly Network network; - private readonly ICheckpoints checkpoints; private readonly IDateTimeProvider dateTimeProvider; + private readonly IBlockStore blockStore; private readonly ConsensusSettings consensusSettings; private CachePerformanceSnapshot latestPerformanceSnapShot; private IScriptAddressReader scriptAddressReader; - private int lastCheckpointHeight; private readonly Random random; - public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, - StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null) + public CachedCoinView(Network network, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, + StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null) { Guard.NotNull(coindb, nameof(CachedCoinView.coindb)); this.coindb = coindb; this.logger = loggerFactory.CreateLogger(this.GetType().FullName); this.network = network; - this.checkpoints = checkpoints; this.dateTimeProvider = dateTimeProvider; this.consensusSettings = consensusSettings; this.stakeChainStore = stakeChainStore; this.rewindDataIndexCache = rewindDataIndexCache; + this.blockStore = blockStore; this.lockobj = new object(); this.cachedUtxoItems = new Dictionary(); this.cacheBalancesByDestination = new Dictionary>(); @@ -160,8 +160,6 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, this.scriptAddressReader = scriptAddressReader; this.random = new Random(); - this.lastCheckpointHeight = this.checkpoints.GetLastCheckpointHeight(); - this.MaxCacheSizeBytes = consensusSettings.MaxCoindbCacheInMB * 1024 * 1024; this.CacheFlushTimeIntervalSeconds = consensusSettings.CoindbIbdFlushMin * 60; @@ -169,6 +167,54 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); } + public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + { + this.coindb.Initialize(chainTip); + + HashHeightPair coinViewTip = this.coindb.GetTipHash(); + + while (true) + { + ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(coinViewTip.Hash); + + if (pendingTip != null) + break; + + if ((coinViewTip.Height % 100) == 0) + this.logger.LogInformation("Rewinding coin view from '{0}' to {1}.", coinViewTip, chainTip); + + // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip. + // The node will complete loading before connecting to peers so the chain will never know that a reorg happened. + coinViewTip = this.coindb.Rewind(new HashHeightPair(chainTip)); + } + + // If the coin view is behind the block store then catch up from the block store. + if (coinViewTip.Height < chainTip.Height) + { + foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(chainIndexer[coinViewTip.Hash], chainIndexer)) + { + if (block == null) + break; + + if ((chainedHeader.Height % 10000) == 0) + this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); + + var ruleContext = new PosRuleContext() + { + ValidationContext = new ValidationContext() { ChainedHeaderToValidate = chainedHeader, BlockToValidate = block }, + SkipValidation = true + }; + + foreach (var rule in consensusRulesContainer.FullValidationRules) + { + rule.RunAsync(ruleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + } + } + } + + this.logger.LogInformation("Coin view initialized at '{0}'.", this.coindb.GetTipHash()); + } + public HashHeightPair GetTipHash() { if (this.blockHash == null) @@ -223,6 +269,10 @@ public void CacheCoins(OutPoint[] utxos) /// public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { + //var address = BitcoinAddress.Create("XXEHUS8dDHtn7M8AcrgweVcozweJcGGy4i", this.network); + //var x = PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(address.ScriptPubKey); + //var b = GetBalance(x).ToList(); + Guard.NotNull(utxos, nameof(utxos)); var result = new FetchCoinsResponse(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index c3c477802d..55c7054432 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using NBitcoin; +using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.Consensus.CoinViews @@ -10,6 +11,14 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews /// public interface ICoinView { + /// + /// Initializes the coin view. + /// + /// The chain tip. + /// The chain indexer. + /// The consensus rules container. + void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer); + /// /// Retrieves the block hash of the current tip of the coinview. /// diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs index 120a4561fa..08b3387b68 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -59,7 +59,7 @@ public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) /// /// The 'skipMissing' flag allows us to rewind coind db's that have incomplete balance information. /// - protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates, bool skipMissing = false) + protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates) { foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) { @@ -69,8 +69,6 @@ protected void AdjustBalance(IDbBatch batch, Dictionary + public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + { + throw new NotImplementedException(); + } + /// public HashHeightPair GetTipHash() { diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs index 0db483a139..5bce36377c 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs @@ -33,7 +33,7 @@ public class PosConsensusRuleEngine : PowConsensusRuleEngine public PosConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IStakeChain stakeChain, IStakeValidator stakeValidator, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore = null) : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) { this.StakeChain = stakeChain; diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs index c0b1266059..f1dd483c32 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using NBitcoin; @@ -10,7 +9,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.CoinViews; -using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using TracerAttributes; @@ -34,7 +32,7 @@ public class PowConsensusRuleEngine : ConsensusRuleEngine public PowConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore = null) : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, chainState, invalidBlockHashStore, nodeStats, consensusRulesContainer) { this.logger = loggerFactory.CreateLogger(this.GetType().FullName); @@ -75,51 +73,7 @@ public override void Initialize(ChainedHeader chainTip) { base.Initialize(chainTip); - var coinDatabase = ((CachedCoinView)this.UtxoSet).ICoindb; - coinDatabase.Initialize(chainTip); - - HashHeightPair coinViewTip = coinDatabase.GetTipHash(); - - while (true) - { - ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(coinViewTip.Hash); - - if (pendingTip != null) - break; - - if ((coinViewTip.Height % 100) == 0) - this.logger.LogInformation("Rewinding coin view from '{0}' to {1}.", coinViewTip, chainTip); - - // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip. - // The node will complete loading before connecting to peers so the chain will never know that a reorg happened. - coinViewTip = coinDatabase.Rewind(new HashHeightPair(chainTip)); - } - - // If the coin view is behind the block store then catch up from the block store. - if (coinViewTip.Height < chainTip.Height) - { - foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(this.ChainIndexer[0], this.ChainIndexer)) - { - if (block == null) - break; - - if ((chainedHeader.Height % 10000) == 0) - this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); - - var ruleContext = new PosRuleContext() - { - ValidationContext = new ValidationContext() { ChainedHeaderToValidate = chainedHeader, BlockToValidate = block }, - SkipValidation = true - }; - - foreach (var rule in this.consensusRulesContainer.FullValidationRules) - { - rule.RunAsync(ruleContext).ConfigureAwait(false).GetAwaiter().GetResult(); - } - } - } - - this.logger.LogInformation("Coin view initialized at '{0}'.", coinDatabase.GetTipHash()); + this.UtxoSet.Initialize(chainTip, this.ChainIndexer, this.consensusRulesContainer); } public override async Task FullValidationAsync(ChainedHeader header, Block block) diff --git a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs index 380f1bfcb4..da7f97c7e7 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using NBitcoin; +using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.MemoryPool.Interfaces; @@ -49,6 +50,11 @@ public MempoolCoinView(Network network, ICoinView inner, ITxMempool memPool, Sch this.Set = new UnspentOutputSet(); } + public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + { + throw new NotImplementedException(); + } + /// /// Gets the unspent transaction output set. /// diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs index ba108d5aad..01a120a496 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs @@ -31,7 +31,7 @@ public PoAConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDa NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, ISlotsManager slotsManager, PoABlockHeaderValidator poaHeaderValidator, VotingManager votingManager, IFederationManager federationManager, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, - IFederationHistory federationHistory, IBlockStore blockStore) + IFederationHistory federationHistory, IBlockStore blockStore = null) : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) { this.SlotsManager = slotsManager; diff --git a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs index 6a3c7651cb..b0452cef5f 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs @@ -9,7 +9,6 @@ using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Settings; -using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.IntegrationTests.Common; @@ -46,7 +45,11 @@ public void TestDBreezeSerialization() Block genesis = ctx.Network.GetGenesis(); var genesisChainedHeader = new ChainedHeader(genesis.Header, ctx.Network.GenesisHash, 0); ChainedHeader chained = this.MakeNext(genesisChainedHeader, ctx.Network); - ctx.Coindb.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), new Coins(0, genesis.Transactions[0].Outputs.First(), true)) }, new Dictionary>(), new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); + var coins = new Coins(0, genesis.Transactions[0].Outputs.First(), true); + TxDestination txDestination = PayToPubkeyTemplate.Instance.ExtractScriptPubKeyParameters(coins.TxOut.ScriptPubKey).Hash; + ctx.Coindb.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), coins) }, + new Dictionary>() { { txDestination, new Dictionary { { 0, coins.TxOut.Value } } } }, + new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); Assert.NotNull(ctx.Coindb.FetchCoins(new[] { new OutPoint(genesis.Transactions[0], 0) }).UnspentOutputs.Values.FirstOrDefault().Coins); Assert.Null(ctx.Coindb.FetchCoins(new[] { new OutPoint() }).UnspentOutputs.Values.FirstOrDefault().Coins); @@ -72,7 +75,7 @@ public void TestCacheCoinView() ChainedHeader chained = this.MakeNext(genesisChainedHeader, ctx.Network); var dateTimeProvider = new DateTimeProvider(); - var cacheCoinView = new CachedCoinView(this.network, new Checkpoints(), ctx.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); + var cacheCoinView = new CachedCoinView(this.network, ctx.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); cacheCoinView.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), new Coins(0, genesis.Transactions[0].Outputs.First(), true)) }, new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); Assert.NotNull(cacheCoinView.FetchCoins(new[] { new OutPoint(genesis.Transactions[0], 0) }).UnspentOutputs.Values.FirstOrDefault().Coins); @@ -104,7 +107,7 @@ public void CanRewind() using (NodeContext nodeContext = NodeContext.Create(this)) { var dateTimeProvider = new DateTimeProvider(); - var cacheCoinView = new CachedCoinView(this.network, new Checkpoints(), nodeContext.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); + var cacheCoinView = new CachedCoinView(this.network, nodeContext.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); var tester = new CoinViewTester(cacheCoinView); List<(Coins, OutPoint)> coinsA = tester.CreateCoins(5); diff --git a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs index 39b69d75b9..817f338419 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using NBitcoin; +using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Utilities; using ReaderWriterLock = NBitcoin.ReaderWriterLock; @@ -33,6 +34,12 @@ public TestInMemoryCoinView(HashHeightPair tipHash) this.tipHash = tipHash; } + /// + public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + { + throw new NotImplementedException(); + } + /// public HashHeightPair GetTipHash() { diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs index 464e704a9f..2e062ad0d8 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs @@ -207,8 +207,7 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") this.NodeSettings = new NodeSettings(this.network, args: new string[] { "-checkpoints" }); var consensusSettings = new ConsensusSettings(this.NodeSettings); - var checkPoints = new Checkpoints(this.network, consensusSettings); - this.cachedCoinView = new CachedCoinView(this.network, checkPoints, inMemoryCoinView, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, this.NodeSettings, new Mock().Object), consensusSettings); + this.cachedCoinView = new CachedCoinView(this.network, inMemoryCoinView, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, this.NodeSettings, new Mock().Object), consensusSettings); var nodeDeployments = new NodeDeployments(this.network, this.ChainIndexer); From 2dea122fa365c3a08e1bd10afa13ce949d24cd0e Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 18:58:43 +1000 Subject: [PATCH 08/42] Reduce changes --- .../ColdStakingControllerTest.cs | 2 +- .../CoinViews/CoinviewTests.cs | 2 +- .../TestChainFactory.cs | 2 +- .../CoinViews/CachedCoinView.cs | 2 +- src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs | 5 +++-- .../PoW/SmartContractMinerTests.cs | 7 ++++--- 6 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs b/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs index 21795f9678..8709fa2ab7 100644 --- a/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs +++ b/src/Stratis.Bitcoin.Features.ColdStaking.Tests/ColdStakingControllerTest.cs @@ -167,7 +167,7 @@ private void CreateMempoolManager() ConsensusRuleEngine consensusRuleEngine = new PosConsensusRuleEngine(this.Network, this.loggerFactory, this.dateTimeProvider, this.chainIndexer, this.nodeDeployments, this.consensusSettings, checkpoints.Object, this.coinView.Object, this.stakeChain.Object, - this.stakeValidator.Object, chainState, new InvalidBlockHashStore(this.dateTimeProvider), new Mock().Object, new Mock().Object, this.asyncProvider, consensusRulesContainer, null) + this.stakeValidator.Object, chainState, new InvalidBlockHashStore(this.dateTimeProvider), new Mock().Object, new Mock().Object, this.asyncProvider, consensusRulesContainer) .SetupRulesEngineParent(); // Create mempool validator. diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs index e39ec6b8b0..8f882403a4 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs @@ -51,7 +51,7 @@ public CoinviewTests() this.rewindDataIndexCache = new RewindDataIndexCache(this.dateTimeProvider, this.network, new FinalizedBlockInfoRepository(new HashHeightPair()), new Checkpoints()); - this.cachedCoinView = new CachedCoinView(this.network, this.coindb, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new ConsensusSettings(new NodeSettings(this.network)), this.stakeChainStore, this.rewindDataIndexCache); + this.cachedCoinView = new CachedCoinView(this.network, new Checkpoints(), this.coindb, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new ConsensusSettings(new NodeSettings(this.network)), this.stakeChainStore, this.rewindDataIndexCache); this.rewindDataIndexCache.Initialize(this.chainIndexer.Height, this.cachedCoinView); diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs index b81a583a44..cd152c776e 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/TestChainFactory.cs @@ -123,7 +123,7 @@ public static async Task CreateAsync(Network network, string d testChainContext.InitialBlockDownloadState = new InitialBlockDownloadState(testChainContext.ChainState, testChainContext.Network, consensusSettings, new Checkpoints(), testChainContext.DateTimeProvider); var inMemoryCoinView = new InMemoryCoinView(new HashHeightPair(testChainContext.ChainIndexer.Tip)); - var cachedCoinView = new CachedCoinView(network, inMemoryCoinView, DateTimeProvider.Default, testChainContext.LoggerFactory, new NodeStats(testChainContext.DateTimeProvider, testChainContext.NodeSettings, new Mock().Object), new ConsensusSettings(testChainContext.NodeSettings)); + var cachedCoinView = new CachedCoinView(network, new Checkpoints(), inMemoryCoinView, DateTimeProvider.Default, testChainContext.LoggerFactory, new NodeStats(testChainContext.DateTimeProvider, testChainContext.NodeSettings, new Mock().Object), new ConsensusSettings(testChainContext.NodeSettings)); var dataFolder = new DataFolder(TestBase.AssureEmptyDir(dataDir).FullName); testChainContext.PeerAddressManager = diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index 64bf3dada7..f8f30e6d53 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -138,7 +138,7 @@ public long GetScriptSize private readonly Random random; - public CachedCoinView(Network network, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, + public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null) { Guard.NotNull(coindb, nameof(CachedCoinView.coindb)); diff --git a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs index b0452cef5f..5a6d28e632 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/CoinViewTests.cs @@ -9,6 +9,7 @@ using Stratis.Bitcoin.Base; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Settings; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.Consensus; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.IntegrationTests.Common; @@ -75,7 +76,7 @@ public void TestCacheCoinView() ChainedHeader chained = this.MakeNext(genesisChainedHeader, ctx.Network); var dateTimeProvider = new DateTimeProvider(); - var cacheCoinView = new CachedCoinView(this.network, ctx.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); + var cacheCoinView = new CachedCoinView(this.network, new Checkpoints(), ctx.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); cacheCoinView.SaveChanges(new UnspentOutput[] { new UnspentOutput(new OutPoint(genesis.Transactions[0], 0), new Coins(0, genesis.Transactions[0].Outputs.First(), true)) }, new HashHeightPair(genesisChainedHeader), new HashHeightPair(chained)); Assert.NotNull(cacheCoinView.FetchCoins(new[] { new OutPoint(genesis.Transactions[0], 0) }).UnspentOutputs.Values.FirstOrDefault().Coins); @@ -107,7 +108,7 @@ public void CanRewind() using (NodeContext nodeContext = NodeContext.Create(this)) { var dateTimeProvider = new DateTimeProvider(); - var cacheCoinView = new CachedCoinView(this.network, nodeContext.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); + var cacheCoinView = new CachedCoinView(this.network, new Checkpoints(), nodeContext.Coindb, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), new ConsensusSettings(new NodeSettings(this.network))); var tester = new CoinViewTester(cacheCoinView); List<(Coins, OutPoint)> coinsA = tester.CreateCoins(5); diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs index 2e062ad0d8..2d1d6bb878 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs @@ -207,7 +207,9 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") this.NodeSettings = new NodeSettings(this.network, args: new string[] { "-checkpoints" }); var consensusSettings = new ConsensusSettings(this.NodeSettings); - this.cachedCoinView = new CachedCoinView(this.network, inMemoryCoinView, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, this.NodeSettings, new Mock().Object), consensusSettings); + var checkPoints = new Checkpoints(this.network, consensusSettings); + + this.cachedCoinView = new CachedCoinView(this.network, checkPoints, inMemoryCoinView, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, this.NodeSettings, new Mock().Object), consensusSettings); var nodeDeployments = new NodeDeployments(this.network, this.ChainIndexer); @@ -256,8 +258,7 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object), asyncProvider, - consensusRulesContainer, - new Mock().Object) + consensusRulesContainer) .SetupRulesEngineParent(); var finalizedBlockInfoRepository = new FinalizedBlockInfoRepository(new HashHeightPair()); From f4c8f8666506510ee719672e58566ad77dd92f6a Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 19:10:20 +1000 Subject: [PATCH 09/42] Reduce changes --- .../Rules/PosConsensusRuleEngine.cs | 4 ++-- .../Rules/PowConsensusRuleEngine.cs | 4 +--- .../TestChainFactory.cs | 5 ++--- src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs | 4 ++-- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs index 5bce36377c..39cd3762fa 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs @@ -33,8 +33,8 @@ public class PosConsensusRuleEngine : PowConsensusRuleEngine public PosConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IStakeChain stakeChain, IStakeValidator stakeValidator, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore = null) - : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IRewindDataIndexCache rewindDataIndexCache, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer) + : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer) { this.StakeChain = stakeChain; this.StakeValidator = stakeValidator; diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs index f1dd483c32..ef00e67f95 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs @@ -27,12 +27,11 @@ public class PowConsensusRuleEngine : ConsensusRuleEngine public ICoinView UtxoSet { get; } private readonly CoinviewPrefetcher prefetcher; - private readonly IBlockStore blockStore; private readonly ConsensusRulesContainer consensusRulesContainer; public PowConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDateTimeProvider dateTimeProvider, ChainIndexer chainIndexer, NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, - IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, IBlockStore blockStore = null) + IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer) : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, chainState, invalidBlockHashStore, nodeStats, consensusRulesContainer) { this.logger = loggerFactory.CreateLogger(this.GetType().FullName); @@ -41,7 +40,6 @@ public PowConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDa this.UtxoSet = utxoSet; this.prefetcher = new CoinviewPrefetcher(this.UtxoSet, chainIndexer, loggerFactory, asyncProvider, checkpoints); - this.blockStore = blockStore; } /// diff --git a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs index 086b2c3a60..12110bd842 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs @@ -117,8 +117,7 @@ public static async Task CreatePosAsync(Network network, Scri var stakeChain = new StakeChainStore(network, chain, null, loggerFactory); ConsensusRuleEngine consensusRules = new PosConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), inMemoryCoinView, stakeChain, new StakeValidator(network, stakeChain, chain, inMemoryCoinView, loggerFactory), chainState, new InvalidBlockHashStore(dateTimeProvider), - new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), new RewindDataIndexCache(dateTimeProvider, network, - finalizedBlockInfoRepository, new Checkpoints()), asyncProvider, consensusRulesContainer, new Mock().Object).SetupRulesEngineParent(); + new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), new RewindDataIndexCache(dateTimeProvider, network, finalizedBlockInfoRepository, new Checkpoints()), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); @@ -238,7 +237,7 @@ public static async Task CreateAsync(Network network, Script ConsensusRuleEngine consensusRules = new PowConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), - asyncProvider, consensusRulesContainer, new Mock().Object).SetupRulesEngineParent(); + asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs index 01a120a496..684766e3df 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs @@ -31,8 +31,8 @@ public PoAConsensusRuleEngine(Network network, ILoggerFactory loggerFactory, IDa NodeDeployments nodeDeployments, ConsensusSettings consensusSettings, ICheckpoints checkpoints, ICoinView utxoSet, IChainState chainState, IInvalidBlockHashStore invalidBlockHashStore, INodeStats nodeStats, ISlotsManager slotsManager, PoABlockHeaderValidator poaHeaderValidator, VotingManager votingManager, IFederationManager federationManager, IAsyncProvider asyncProvider, ConsensusRulesContainer consensusRulesContainer, - IFederationHistory federationHistory, IBlockStore blockStore = null) - : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer, blockStore) + IFederationHistory federationHistory) + : base(network, loggerFactory, dateTimeProvider, chainIndexer, nodeDeployments, consensusSettings, checkpoints, utxoSet, chainState, invalidBlockHashStore, nodeStats, asyncProvider, consensusRulesContainer) { this.SlotsManager = slotsManager; this.PoaHeaderValidator = poaHeaderValidator; From 645d6daed2ee055bbc2f9f472d711a7ffbdc847a Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 19:12:18 +1000 Subject: [PATCH 10/42] Reduce changes --- .../Rules/PosConsensusRuleEngine.cs | 1 - .../Rules/PowConsensusRuleEngine.cs | 1 - src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs index 39cd3762fa..69b224cc04 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PosConsensusRuleEngine.cs @@ -9,7 +9,6 @@ using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.Interfaces; using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders; -using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using TracerAttributes; diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs index ef00e67f95..c62804909b 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/PowConsensusRuleEngine.cs @@ -9,7 +9,6 @@ using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.CoinViews; -using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using TracerAttributes; diff --git a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs index 684766e3df..bafaffb8f5 100644 --- a/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs +++ b/src/Stratis.Bitcoin.Features.PoA/PoAConsensusRuleEngine.cs @@ -9,7 +9,6 @@ using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Features.Consensus.Rules; using Stratis.Bitcoin.Features.PoA.Voting; -using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.PoA From 87e83e6ce9c02d1a263e23dd55021e105a4c95e5 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 19:17:18 +1000 Subject: [PATCH 11/42] Reduce changes --- .../TestChainFactory.cs | 3 +-- .../PoW/SmartContractMinerTests.cs | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs index 12110bd842..b588aeaf41 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool.Tests/TestChainFactory.cs @@ -236,8 +236,7 @@ public static async Task CreateAsync(Network network, Script var deployments = new NodeDeployments(network, chain); ConsensusRuleEngine consensusRules = new PowConsensusRuleEngine(network, loggerFactory, dateTimeProvider, chain, deployments, consensusSettings, new Checkpoints(), - inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), - asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); + inMemoryCoinView, chainState, new InvalidBlockHashStore(dateTimeProvider), new NodeStats(dateTimeProvider, nodeSettings, new Mock().Object), asyncProvider, consensusRulesContainer).SetupRulesEngineParent(); IConsensusManager consensus = ConsensusManagerHelper.CreateConsensusManager(network, dataDir, chainState, chainIndexer: chain, consensusRules: consensusRules, inMemoryCoinView: inMemoryCoinView); diff --git a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs index 2d1d6bb878..7794cce1b3 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/PoW/SmartContractMinerTests.cs @@ -208,7 +208,6 @@ public async Task InitializeAsync([CallerMemberName] string callingMethod = "") this.NodeSettings = new NodeSettings(this.network, args: new string[] { "-checkpoints" }); var consensusSettings = new ConsensusSettings(this.NodeSettings); var checkPoints = new Checkpoints(this.network, consensusSettings); - this.cachedCoinView = new CachedCoinView(this.network, checkPoints, inMemoryCoinView, dateTimeProvider, this.loggerFactory, new NodeStats(dateTimeProvider, this.NodeSettings, new Mock().Object), consensusSettings); var nodeDeployments = new NodeDeployments(this.network, this.ChainIndexer); From fd363c7f78ae318a30b3be8bc65ec60a15503cae Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 20:30:02 +1000 Subject: [PATCH 12/42] Flush after rebuild --- .../CoinViews/CachedCoinView.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index f8f30e6d53..fa04640f0f 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -197,7 +197,10 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen break; if ((chainedHeader.Height % 10000) == 0) + { + this.Flush(true); this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); + } var ruleContext = new PosRuleContext() { @@ -210,6 +213,8 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen rule.RunAsync(ruleContext).ConfigureAwait(false).GetAwaiter().GetResult(); } } + + this.Flush(true); } this.logger.LogInformation("Coin view initialized at '{0}'.", this.coindb.GetTipHash()); From 4f4fda30380ac17c49c330894c04e528a8ee5ed3 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 12 Jun 2022 23:56:18 +1000 Subject: [PATCH 13/42] Index only PoS --- .../CoinViews/CoinviewTests.cs | 2 +- .../CoinViews/CachedCoinView.cs | 68 +++++++++++++------ .../CoinViews/Coindb/DBreezeCoindb.cs | 2 +- .../CoinViews/Coindb/ICoindb.cs | 2 +- .../CoinViews/Coindb/LeveldbCoindb.cs | 8 +-- .../CoinViews/Coindb/RocksDbCoindb.cs | 8 +-- .../CoinViews/InMemoryCoinView.cs | 4 ++ 7 files changed, 63 insertions(+), 31 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs index 8f882403a4..c2212e3f9e 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs @@ -43,7 +43,7 @@ public CoinviewTests() this.nodeStats = new NodeStats(this.dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object); this.coindb = new DBreezeCoindb(this.network, this.dataFolder, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new DBreezeSerializer(this.network.Consensus.ConsensusFactory)); - this.coindb.Initialize(new ChainedHeader(this.network.GetGenesis().Header, this.network.GenesisHash, 0)); + this.coindb.Initialize(new ChainedHeader(this.network.GetGenesis().Header, this.network.GenesisHash, 0), false); this.chainIndexer = new ChainIndexer(this.network); this.stakeChainStore = new StakeChainStore(this.network, this.chainIndexer, (IStakedb)this.coindb, this.loggerFactory); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index fa04640f0f..9b172357cf 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; using Stratis.Bitcoin.Features.Consensus.ProvenBlockHeaders; +using Stratis.Bitcoin.Features.Consensus.Rules.CommonRules; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; using Stratis.Bitcoin.Utilities.Extensions; @@ -121,6 +123,8 @@ public long GetScriptSize /// The getter violates the lock contract on , but the lock here is unnecessary as the is marked as readonly. private int cacheCount => this.cachedUtxoItems.Count; + public bool AddressIndexingEnabled { get; private set; } + /// Number of items in the rewind data. /// The getter violates the lock contract on , but the lock here is unnecessary as the is marked as readonly. private int rewindDataCount => this.cachedRewindData.Count; @@ -132,6 +136,7 @@ public long GetScriptSize private readonly Network network; private readonly IDateTimeProvider dateTimeProvider; private readonly IBlockStore blockStore; + private readonly CancellationTokenSource cancellationToken; private readonly ConsensusSettings consensusSettings; private CachePerformanceSnapshot latestPerformanceSnapShot; private IScriptAddressReader scriptAddressReader; @@ -139,7 +144,7 @@ public long GetScriptSize private readonly Random random; public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, - StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null) + StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null, INodeLifetime nodeLifetime = null) { Guard.NotNull(coindb, nameof(CachedCoinView.coindb)); @@ -151,6 +156,7 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, this.stakeChainStore = stakeChainStore; this.rewindDataIndexCache = rewindDataIndexCache; this.blockStore = blockStore; + this.cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(nodeLifetime.ApplicationStopping); this.lockobj = new object(); this.cachedUtxoItems = new Dictionary(); this.cacheBalancesByDestination = new Dictionary>(); @@ -165,11 +171,13 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, if (nodeStats.DisplayBenchStats) nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); + + this.AddressIndexingEnabled = this.network.Consensus.IsProofOfStake; } public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) { - this.coindb.Initialize(chainTip); + this.coindb.Initialize(chainTip, this.AddressIndexingEnabled); HashHeightPair coinViewTip = this.coindb.GetTipHash(); @@ -191,30 +199,49 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen // If the coin view is behind the block store then catch up from the block store. if (coinViewTip.Height < chainTip.Height) { - foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(chainIndexer[coinViewTip.Hash], chainIndexer)) + try { - if (block == null) - break; + var loadCoinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); + var saveCoinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); + var coinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); - if ((chainedHeader.Height % 10000) == 0) + foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(chainIndexer[coinViewTip.Hash], chainIndexer, this.cancellationToken, batchSize: 1000)) { - this.Flush(true); - this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); - } + if (block == null) + break; - var ruleContext = new PosRuleContext() - { - ValidationContext = new ValidationContext() { ChainedHeaderToValidate = chainedHeader, BlockToValidate = block }, - SkipValidation = true - }; + if ((chainedHeader.Height % 10000) == 0) + { + this.Flush(true); + this.logger.LogInformation("Rebuilding coin view from '{0}' to {1}.", chainedHeader, chainTip); + } - foreach (var rule in consensusRulesContainer.FullValidationRules) - { - rule.RunAsync(ruleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + var utxoRuleContext = new PosRuleContext() + { + ValidationContext = new ValidationContext() { ChainedHeaderToValidate = chainedHeader, BlockToValidate = block }, + SkipValidation = true + }; + + // Loads the coins spent by this block into utxoRuleContext.UnspentOutputSet. + loadCoinViewRule.RunAsync(utxoRuleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + + // Spends the coins. + coinViewRule.RunAsync(utxoRuleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + + // Saves the changes to the coinview. + saveCoinViewRule.RunAsync(utxoRuleContext).ConfigureAwait(false).GetAwaiter().GetResult(); } } + finally + { + this.Flush(true); - this.Flush(true); + if (this.cancellationToken.IsCancellationRequested) + { + this.logger.LogDebug("Rebuilding cancelled due to application stopping."); + throw new OperationCanceledException(); + } + } } this.logger.LogInformation("Coin view initialized at '{0}'.", this.coindb.GetTipHash()); @@ -435,7 +462,8 @@ public void Flush(bool force = true) this.logger.LogDebug("Flushing {0} items.", modify.Count); - this.coindb.SaveChanges(modify, this.cacheBalancesByDestination, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); + if (modify.Count != 0 || this.cacheBalancesByDestination.Count != 0) + this.coindb.SaveChanges(modify, this.cacheBalancesByDestination, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); // All the cached utxos are now on disk so we can clear the cached entry list. this.cachedUtxoItems.Clear(); @@ -727,7 +755,7 @@ private void AddBenchStats(StringBuilder log) private void RecordBalanceChange(Script scriptPubKey, long satoshis, uint height) { - if (scriptPubKey.Length == 0 || satoshis == 0) + if (!this.AddressIndexingEnabled || scriptPubKey.Length == 0 || satoshis == 0) return; foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs index 9ef54229bb..f74d7123f2 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs @@ -65,7 +65,7 @@ public DBreezeCoindb(Network network, string folder, IDateTimeProvider dateTimeP nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); } - public void Initialize(ChainedHeader chainTip) + public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) { Block genesis = this.network.GetGenesis(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index b41e081d5d..bec74d9868 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -11,7 +11,7 @@ public interface ICoindb { /// Initialize the coin database. /// The current chain's tip. - void Initialize(ChainedHeader chainTip); + void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled); /// /// Retrieves the block hash of the current tip of the coinview. diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index 3354a3d721..2ba03d9ecb 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -58,12 +58,12 @@ public LevelDbCoindb(Network network, string dataFolder, IDateTimeProvider dateT nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); } - public void Initialize(ChainedHeader chainTip) + public void Initialize(ChainedHeader chainTip, bool addressIndexingEnabled) { // Open a connection to a new DB and create if not found this.coinDb = new LevelDb(this.dataFolder); - EnsureCoinDatabaseIntegrity(chainTip); + EnsureCoinDatabaseIntegrity(chainTip, addressIndexingEnabled); Block genesis = this.network.GetGenesis(); @@ -79,12 +79,12 @@ public void Initialize(ChainedHeader chainTip) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) + private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip, bool balanceIndexingEnabled) { this.logger.LogInformation("Checking coin database integrity..."); // If the balance table is empty then rebuild the coin db. - if (!this.coinDb.GetAll(balanceTable).Any()) + if (balanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) { this.logger.LogInformation($"Rebuilding coin database to include balance information."); this.coinDb.Clear(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs index 312201c31f..6956a1bf22 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs @@ -58,12 +58,12 @@ public RocksDbCoindb(Network network, string dataFolder, IDateTimeProvider dateT nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); } - public void Initialize(ChainedHeader chainTip) + public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) { // Open a connection to a new DB and create if not found this.coinDb = new RocksDb(this.dataFolder); - EnsureCoinDatabaseIntegrity(chainTip); + EnsureCoinDatabaseIntegrity(chainTip, balanceIndexingEnabled); Block genesis = this.network.GetGenesis(); @@ -79,12 +79,12 @@ public void Initialize(ChainedHeader chainTip) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) + private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip, bool balanceIndexingEnabled) { this.logger.LogInformation("Checking coin database integrity..."); // If the balance table is empty then rebuild the coin db. - if (!this.coinDb.GetAll(balanceTable).Any()) + if (balanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) { this.logger.LogInformation($"Rebuilding coin database to include balance information."); this.coinDb.Clear(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs index 09817491fd..18211d24da 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs @@ -39,6 +39,10 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen throw new NotImplementedException(); } + public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) + { + } + /// public HashHeightPair GetTipHash() { From 2793a15ff8fc2465d6c72f4ee55f5da9b4ae2ba5 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 13 Jun 2022 01:14:08 +1000 Subject: [PATCH 14/42] Optimize --- src/Stratis.Bitcoin.Features.Consensus/UnspentOutputSet.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/UnspentOutputSet.cs b/src/Stratis.Bitcoin.Features.Consensus/UnspentOutputSet.cs index 41b71318de..2aa58e4ce2 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/UnspentOutputSet.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/UnspentOutputSet.cs @@ -52,9 +52,12 @@ public void Update(Network network, Transaction transaction, int height) } } - foreach (IndexedTxOut output in transaction.Outputs.AsIndexedOutputs()) + // Hash calculations are slow. Do this one outside the loop... + var txHash = transaction.GetHash(); + + foreach (IndexedTxOut output in transaction.Outputs.AsIndexedOutputs().ToList()) { - var outpoint = output.ToOutPoint(); + var outpoint = new OutPoint() { Hash = txHash, N = output.N }; var coinbase = transaction.IsCoinBase; var coinstake = network.Consensus.IsProofOfStake ? transaction.IsCoinStake : false; From 577a0327b75b3e8e4e4eed9e2f6d25866d877474 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 13 Jun 2022 13:16:07 +1000 Subject: [PATCH 15/42] Optimize / fix tests --- .../CoinViews/CachedCoinView.cs | 4 ++++ .../CoinViews/CoinviewHelper.cs | 4 +++- src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs | 4 ++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index 9b172357cf..621ccb00fc 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -204,6 +204,7 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen var loadCoinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); var saveCoinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); var coinViewRule = consensusRulesContainer.FullValidationRules.OfType().Single(); + var deploymentsRule = consensusRulesContainer.FullValidationRules.OfType().Single(); foreach ((ChainedHeader chainedHeader, Block block) in this.blockStore.BatchBlocksFrom(chainIndexer[coinViewTip.Hash], chainIndexer, this.cancellationToken, batchSize: 1000)) { @@ -222,6 +223,9 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen SkipValidation = true }; + // Set context flags. + deploymentsRule.RunAsync(utxoRuleContext).ConfigureAwait(false).GetAwaiter().GetResult(); + // Loads the coins spent by this block into utxoRuleContext.UnspentOutputSet. loadCoinViewRule.RunAsync(utxoRuleContext).ConfigureAwait(false).GetAwaiter().GetResult(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs index bc3f268f2a..73ad11b883 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs @@ -20,8 +20,10 @@ public OutPoint[] GetIdsToFetch(Block block, bool enforceBIP30) { if (enforceBIP30) { + // Calculate the hash outside the loop. + var txId = tx.GetHash(); foreach (var utxo in tx.Outputs.AsIndexedOutputs()) - ids.Add(utxo.ToOutPoint()); + ids.Add(new OutPoint() { Hash = txId, N = utxo.N }); } if (!tx.IsCoinBase) diff --git a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs index ba6dc565f1..055a6a0cd3 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs @@ -29,7 +29,7 @@ public NodeContext(object caller, string name, Network network) var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); this.Coindb = new LevelDbCoindb(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer, new ScriptAddressReader()); - this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0)); + this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0), false); this.cleanList = new List { (IDisposable)this.Coindb }; } @@ -68,7 +68,7 @@ public void ReloadPersistentCoinView(ChainedHeader chainTip) var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); this.Coindb = new LevelDbCoindb(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer, new ScriptAddressReader()); - this.Coindb.Initialize(chainTip); + this.Coindb.Initialize(chainTip, false); this.cleanList.Add((IDisposable)this.Coindb); } } From fe2ff6be0ce8168893b9fde7be96d141441e9da0 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 13 Jun 2022 14:50:13 +1000 Subject: [PATCH 16/42] Refactor / fix tests --- .../CoinViews/CachedCoinView.cs | 17 ++++------------- .../CoinViews/Coindb/DBreezeCoindb.cs | 2 ++ .../CoinViews/Coindb/ICoindb.cs | 2 ++ .../CoinViews/Coindb/LeveldbCoindb.cs | 17 +++++++++++------ .../CoinViews/Coindb/RocksDbCoindb.cs | 18 ++++++++++++------ .../CoinViews/InMemoryCoinView.cs | 2 ++ 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index 621ccb00fc..a1812c3a52 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -123,8 +123,6 @@ public long GetScriptSize /// The getter violates the lock contract on , but the lock here is unnecessary as the is marked as readonly. private int cacheCount => this.cachedUtxoItems.Count; - public bool AddressIndexingEnabled { get; private set; } - /// Number of items in the rewind data. /// The getter violates the lock contract on , but the lock here is unnecessary as the is marked as readonly. private int rewindDataCount => this.cachedRewindData.Count; @@ -156,7 +154,7 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, this.stakeChainStore = stakeChainStore; this.rewindDataIndexCache = rewindDataIndexCache; this.blockStore = blockStore; - this.cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(nodeLifetime.ApplicationStopping); + this.cancellationToken = (nodeLifetime == null) ? new CancellationTokenSource(): CancellationTokenSource.CreateLinkedTokenSource(nodeLifetime.ApplicationStopping); this.lockobj = new object(); this.cachedUtxoItems = new Dictionary(); this.cacheBalancesByDestination = new Dictionary>(); @@ -171,13 +169,11 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, if (nodeStats.DisplayBenchStats) nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); - - this.AddressIndexingEnabled = this.network.Consensus.IsProofOfStake; } public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) { - this.coindb.Initialize(chainTip, this.AddressIndexingEnabled); + this.coindb.Initialize(chainTip, this.network.Consensus.IsProofOfStake); HashHeightPair coinViewTip = this.coindb.GetTipHash(); @@ -305,10 +301,6 @@ public void CacheCoins(OutPoint[] utxos) /// public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { - //var address = BitcoinAddress.Create("XXEHUS8dDHtn7M8AcrgweVcozweJcGGy4i", this.network); - //var x = PayToPubkeyHashTemplate.Instance.ExtractScriptPubKeyParameters(address.ScriptPubKey); - //var b = GetBalance(x).ToList(); - Guard.NotNull(utxos, nameof(utxos)); var result = new FetchCoinsResponse(); @@ -466,8 +458,7 @@ public void Flush(bool force = true) this.logger.LogDebug("Flushing {0} items.", modify.Count); - if (modify.Count != 0 || this.cacheBalancesByDestination.Count != 0) - this.coindb.SaveChanges(modify, this.cacheBalancesByDestination, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); + this.coindb.SaveChanges(modify, this.cacheBalancesByDestination, this.innerBlockHash, this.blockHash, this.cachedRewindData.Select(c => c.Value).ToList()); // All the cached utxos are now on disk so we can clear the cached entry list. this.cachedUtxoItems.Clear(); @@ -759,7 +750,7 @@ private void AddBenchStats(StringBuilder log) private void RecordBalanceChange(Script scriptPubKey, long satoshis, uint height) { - if (!this.AddressIndexingEnabled || scriptPubKey.Length == 0 || satoshis == 0) + if (!this.coindb.BalanceIndexingEnabled || scriptPubKey.Length == 0 || satoshis == 0) return; foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs index f74d7123f2..3b94224034 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs @@ -34,6 +34,8 @@ public class DBreezeCoindb : ICoindb, IStakedb, IDisposable private BackendPerformanceSnapshot latestPerformanceSnapShot; + public bool BalanceIndexingEnabled => false; + /// Access to dBreeze database. private readonly DBreezeEngine dBreeze; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index bec74d9868..bc7b9574a6 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -13,6 +13,8 @@ public interface ICoindb /// The current chain's tip. void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled); + bool BalanceIndexingEnabled { get; } + /// /// Retrieves the block hash of the current tip of the coinview. /// diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index 2ba03d9ecb..84df7b7b5c 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -36,6 +36,8 @@ public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable private const int MaxRewindBatchSize = 10000; + public bool BalanceIndexingEnabled { get; private set; } + public LevelDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) @@ -58,12 +60,13 @@ public LevelDbCoindb(Network network, string dataFolder, IDateTimeProvider dateT nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); } - public void Initialize(ChainedHeader chainTip, bool addressIndexingEnabled) + public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) { // Open a connection to a new DB and create if not found this.coinDb = new LevelDb(this.dataFolder); + this.BalanceIndexingEnabled = balanceIndexingEnabled; - EnsureCoinDatabaseIntegrity(chainTip, addressIndexingEnabled); + EnsureCoinDatabaseIntegrity(chainTip); Block genesis = this.network.GetGenesis(); @@ -79,12 +82,12 @@ public void Initialize(ChainedHeader chainTip, bool addressIndexingEnabled) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip, bool balanceIndexingEnabled) + private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { this.logger.LogInformation("Checking coin database integrity..."); // If the balance table is empty then rebuild the coin db. - if (balanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) + if (this.BalanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) { this.logger.LogInformation($"Rebuilding coin database to include balance information."); this.coinDb.Clear(); @@ -280,7 +283,8 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); + if (this.BalanceIndexingEnabled) + Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); batch.Delete(coinsTable, key); } @@ -295,7 +299,8 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); + if (this.BalanceIndexingEnabled) + Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); } res = rewindData.PreviousBlockHash; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs index 6956a1bf22..da7be5694d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs @@ -36,6 +36,8 @@ public class RocksDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable private const int MaxRewindBatchSize = 10000; + public bool BalanceIndexingEnabled { get; private set; } + public RocksDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) @@ -62,8 +64,9 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) { // Open a connection to a new DB and create if not found this.coinDb = new RocksDb(this.dataFolder); + this.BalanceIndexingEnabled = balanceIndexingEnabled; - EnsureCoinDatabaseIntegrity(chainTip, balanceIndexingEnabled); + EnsureCoinDatabaseIntegrity(chainTip); Block genesis = this.network.GetGenesis(); @@ -79,12 +82,12 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip, bool balanceIndexingEnabled) + private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { this.logger.LogInformation("Checking coin database integrity..."); // If the balance table is empty then rebuild the coin db. - if (balanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) + if (this.BalanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) { this.logger.LogInformation($"Rebuilding coin database to include balance information."); this.coinDb.Clear(); @@ -164,7 +167,8 @@ public void SaveChanges(IList unspentOutputs, Dictionary this.performanceCounter.AddInsertTime(o))) { @@ -280,7 +284,8 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); + if (this.BalanceIndexingEnabled) + Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); batch.Delete(coinsTable, key); } @@ -295,7 +300,8 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); + if (this.BalanceIndexingEnabled) + Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); } res = rewindData.PreviousBlockHash; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs index 18211d24da..25c325d0d8 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs @@ -24,6 +24,8 @@ public class InMemoryCoinView : ICoinView, ICoindb /// All access to this object has to be protected by . private HashHeightPair tipHash; + public bool BalanceIndexingEnabled => false; + /// /// Initializes an instance of the object. /// From a577923d29835f6cd722e349c998a298e87e13df Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 13 Jun 2022 19:34:22 +1000 Subject: [PATCH 17/42] Use AddressIndexerCV --- .../AddressIndexing/AddressIndexerCV.cs | 116 ++++++++++++++++++ .../BlockStoreFeature.cs | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs diff --git a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs new file mode 100644 index 0000000000..5d57dc482c --- /dev/null +++ b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NBitcoin; +using Stratis.Bitcoin.Builder.Feature; +using Stratis.Bitcoin.Controllers.Models; +using Stratis.Bitcoin.Features.BlockStore.Models; +using Stratis.Bitcoin.Features.Consensus.CoinViews; +using Stratis.Bitcoin.Interfaces; +using Stratis.Bitcoin.Utilities; + +namespace Stratis.Bitcoin.Features.BlockStore.AddressIndexing +{ + public class AddressIndexerCV : IAddressIndexer + { + private readonly Network network; + private readonly ICoinView coinView; + private readonly ChainIndexer chainIndexer; + private readonly IScriptAddressReader scriptAddressReader; + + public ChainedHeader IndexerTip => GetTip(); + + public IFullNodeFeature InitializingFeature { set; private get; } + + public AddressIndexerCV(Network network, ChainIndexer chainIndexer, IScriptAddressReader scriptAddressReader, ICoinView coinView) + { + this.network = network; + this.coinView = coinView; + this.chainIndexer = chainIndexer; + this.scriptAddressReader = scriptAddressReader; + } + + private ChainedHeader GetTip() + { + HashHeightPair coinViewTip = this.coinView.GetTipHash(); + if (coinViewTip.Hash == this.chainIndexer.Tip.HashBlock) + return this.chainIndexer.Tip; + + ChainedHeader fork = this.chainIndexer[coinViewTip.Hash]; + if (fork == null) + { + // Determine the last usable height. + int height = BinarySearch.BinaryFindFirst(h => this.coinView.GetRewindData(h).PreviousBlockHash.Hash != this.chainIndexer[h].Previous.HashBlock, 0, Math.Min(this.chainIndexer.Height, coinViewTip.Height)); + fork = this.chainIndexer[(height < 0) ? 0 : this.coinView.GetRewindData(height).PreviousBlockHash.Hash]; + } + + this.coinView.Rewind(new HashHeightPair(fork)); + + return fork; + } + + public void Initialize() + { + } + + private TxDestination AddressToDestination(string address) + { + var bitcoinAddress = BitcoinAddress.Create(address, this.network); + return this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, bitcoinAddress.ScriptPubKey).Single(); + } + + public AddressBalancesResult GetAddressBalances(string[] addresses, int minConfirmations = 0) + { + return new AddressBalancesResult() + { + Balances = addresses.Select(address => new AddressBalanceResult() + { + Address = address, + Balance = new Money(this.coinView.GetBalance(AddressToDestination(address)).First().satoshis) + }).ToList() + }; + } + + public LastBalanceDecreaseTransactionModel GetLastBalanceDecreaseTransaction(string address) + { + throw new NotImplementedException(); + } + + private IEnumerable ToDiff(List addressBalanceChanges) + { + for (int i = addressBalanceChanges.Count - 1; i > 0; i--) + { + yield return new AddressBalanceChange() { + BalanceChangedHeight = addressBalanceChanges[i].BalanceChangedHeight, + Deposited = addressBalanceChanges[i].Satoshi < addressBalanceChanges[i - 1].Satoshi, + Satoshi = Math.Abs(addressBalanceChanges[i].Satoshi - addressBalanceChanges[i - 1].Satoshi) + }; + } + } + + /// + public VerboseAddressBalancesResult GetAddressIndexerState(string[] addresses) + { + // If the containing feature is not initialized then wait a bit. + this.InitializingFeature?.WaitInitialized(); + + return new VerboseAddressBalancesResult(this.IndexerTip.Height) + { + BalancesData = addresses.Select(address => new AddressIndexerData() + { + Address = address, + BalanceChanges = ToDiff(this.coinView.GetBalance(AddressToDestination(address)).Select(b => new AddressBalanceChange() + { + BalanceChangedHeight = (int)b.height, + Deposited = b.satoshis >= 0, + Satoshi = Math.Abs(b.satoshis) + }).ToList()).ToList() + }).ToList() + }; + } + + public void Dispose() + { + } + } +} diff --git a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs index 10d2f550eb..00fee016bb 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/BlockStoreFeature.cs @@ -199,7 +199,7 @@ public static IFullNodeBuilder UseBlockStore(this IFullNodeBuilder fullNodeBuild services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); }); }); From 84bd656d8ec52587473228a57588411b71ef3901 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 14 Jun 2022 00:36:19 +1000 Subject: [PATCH 18/42] Support toggling indexing mode --- .../CoinViews/Coindb/BaseCoindb.cs | 102 ++++++++++++++++++ .../CoinViews/Coindb/LeveldbCoindb.cs | 75 ++----------- .../CoinViews/Coindb/RocksDbCoindb.cs | 75 ++----------- 3 files changed, 114 insertions(+), 138 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs index 08b3387b68..b6a9dda6fb 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; @@ -19,6 +20,17 @@ public class BaseCoindb protected static readonly byte balanceTable = 5; protected static readonly byte balanceAdjustmentTable = 6; + /// Database key under which the block hash of the coin view's current tip is stored. + protected static readonly byte[] blockHashKey = new byte[0]; + + /// Database key under which the block hash of the coin view's last indexed tip is stored. + protected static readonly byte[] blockIndexedHashKey = new byte[1]; + + /// Instance logger. + protected ILogger logger; + + public bool BalanceIndexingEnabled { get; protected set; } + /// Access to dBreeze database. protected IDb coinDb; @@ -105,5 +117,95 @@ protected void Update(Dictionary> balanceA value[height] = balance; } } + + public HashHeightPair GetTipHash() + { + if (this.persistedCoinviewTip == null) + { + var row = this.coinDb.Get(blockTable, blockHashKey); + if (row != null) + { + this.persistedCoinviewTip = new HashHeightPair(); + this.persistedCoinviewTip.FromBytes(row); + } + } + + return this.persistedCoinviewTip; + } + + protected HashHeightPair GetIndexedTipHash() + { + var row = this.coinDb.Get(blockTable, blockIndexedHashKey); + if (row != null) + { + var tip = new HashHeightPair(); + tip.FromBytes(row); + return tip; + } + + return null; + } + + protected void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool forceUpdateIndexedHeight = false) + { + this.persistedCoinviewTip = nextBlockHash; + batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); + if (this.BalanceIndexingEnabled || forceUpdateIndexedHeight) + batch.Put(blockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); + } + + protected virtual HashHeightPair RewindInternal(int startHeight, HashHeightPair target) + { + throw new NotImplementedException(); + } + + protected void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) + { + this.logger.LogInformation("Checking coin database integrity..."); + + HashHeightPair maxHeight = new HashHeightPair(chainTip); + + // If the balance table is empty then rebuild the coin db. + if (this.BalanceIndexingEnabled) + { + HashHeightPair indexedTipHash = this.GetIndexedTipHash(); + if (indexedTipHash == null) + { + this.logger.LogInformation($"Rebuilding coin database to include balance information."); + this.coinDb.Clear(); + return; + } + + if (indexedTipHash.Height < chainTip.Height) + maxHeight = indexedTipHash; + } + + var tipHash = GetTipHash(); + + var heightToCheck = chainTip.Height; + + if (heightToCheck > tipHash.Height) + heightToCheck = tipHash.Height; + + // Find the height up to where rewind data is stored above chain tip. + do + { + heightToCheck += 1; + + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); + if (row == null) + break; + } while (true); + + for (int height = heightToCheck - 1; height > maxHeight.Height;) + { + this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); + + // Do a batch of rewinding. + height = RewindInternal(height, maxHeight).Height; + } + + this.logger.LogInformation("Coin database integrity good."); + } } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs index 84df7b7b5c..f4c7f6679a 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs @@ -16,14 +16,8 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews /// public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable { - /// Database key under which the block hash of the coin view's current tip is stored. - private static readonly byte[] blockHashKey = new byte[0]; - private readonly string dataFolder; - /// Instance logger. - private readonly ILogger logger; - /// Specification of the network the node runs on - regtest/testnet/mainnet. private readonly Network network; @@ -36,8 +30,6 @@ public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable private const int MaxRewindBatchSize = 10000; - public bool BalanceIndexingEnabled { get; private set; } - public LevelDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) @@ -82,63 +74,6 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) - { - this.logger.LogInformation("Checking coin database integrity..."); - - // If the balance table is empty then rebuild the coin db. - if (this.BalanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) - { - this.logger.LogInformation($"Rebuilding coin database to include balance information."); - this.coinDb.Clear(); - return; - } - - var heightToCheck = chainTip.Height; - - // Find the height up to where rewind data is stored above chain tip. - do - { - heightToCheck += 1; - - byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); - if (row == null) - break; - - } while (true); - - for (int height = heightToCheck - 1; height > chainTip.Height;) - { - this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); - - // Do a batch of rewinding. - height = RewindInternal(height, new HashHeightPair(chainTip)).Height; - } - - this.logger.LogInformation("Coin database integrity good."); - } - - private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash) - { - this.persistedCoinviewTip = nextBlockHash; - batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); - } - - public HashHeightPair GetTipHash() - { - if (this.persistedCoinviewTip == null) - { - var row = this.coinDb.Get(blockTable, blockHashKey); - if (row != null) - { - this.persistedCoinviewTip = new HashHeightPair(); - this.persistedCoinviewTip.FromBytes(row); - } - } - - return this.persistedCoinviewTip; - } - public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -256,10 +191,12 @@ private bool TryGetCoins(byte[] key, out Coins coins) return true; } - private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) + protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { HashHeightPair res = null; + int indexedHeight = this.GetIndexedTipHash()?.Height ?? -1; + using (var batch = this.coinDb.GetWriteBatch()) { var balanceAdjustments = new Dictionary>(); @@ -283,7 +220,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - if (this.BalanceIndexingEnabled) + if (height <= indexedHeight) Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); batch.Delete(coinsTable, key); @@ -299,7 +236,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - if (this.BalanceIndexingEnabled) + if (height <= indexedHeight) Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); } @@ -308,7 +245,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) AdjustBalance(batch, balanceAdjustments); - this.SetBlockHash(batch, res); + this.SetBlockHash(batch, res, res.Height < indexedHeight); batch.Write(); } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs index da7be5694d..934c034ae9 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs @@ -16,14 +16,8 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews /// public class RocksDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable { - /// Database key under which the block hash of the coin view's current tip is stored. - private static readonly byte[] blockHashKey = new byte[0]; - private readonly string dataFolder; - /// Instance logger. - private readonly ILogger logger; - /// Specification of the network the node runs on - regtest/testnet/mainnet. private readonly Network network; @@ -36,8 +30,6 @@ public class RocksDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable private const int MaxRewindBatchSize = 10000; - public bool BalanceIndexingEnabled { get; private set; } - public RocksDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) @@ -82,63 +74,6 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) - { - this.logger.LogInformation("Checking coin database integrity..."); - - // If the balance table is empty then rebuild the coin db. - if (this.BalanceIndexingEnabled && !this.coinDb.GetAll(balanceTable).Any()) - { - this.logger.LogInformation($"Rebuilding coin database to include balance information."); - this.coinDb.Clear(); - return; - } - - var heightToCheck = chainTip.Height; - - // Find the height up to where rewind data is stored above chain tip. - do - { - heightToCheck += 1; - - byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); - if (row == null) - break; - - } while (true); - - for (int height = heightToCheck - 1; height > chainTip.Height;) - { - this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); - - // Do a batch of rewinding. - height = RewindInternal(height, new HashHeightPair(chainTip)).Height; - } - - this.logger.LogInformation("Coin database integrity good."); - } - - private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash) - { - this.persistedCoinviewTip = nextBlockHash; - batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); - } - - public HashHeightPair GetTipHash() - { - if (this.persistedCoinviewTip == null) - { - var row = this.coinDb.Get(blockTable, blockHashKey); - if (row != null) - { - this.persistedCoinviewTip = new HashHeightPair(); - this.persistedCoinviewTip.FromBytes(row); - } - } - - return this.persistedCoinviewTip; - } - public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -257,10 +192,12 @@ private bool TryGetCoins(byte[] key, out Coins coins) return true; } - private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) + protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { HashHeightPair res = null; + int indexedHeight = this.GetIndexedTipHash()?.Height ?? -1; + using (var batch = this.coinDb.GetWriteBatch()) { var balanceAdjustments = new Dictionary>(); @@ -284,7 +221,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - if (this.BalanceIndexingEnabled) + if (height <= indexedHeight) Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); batch.Delete(coinsTable, key); @@ -300,7 +237,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - if (this.BalanceIndexingEnabled) + if (height <= indexedHeight) Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); } @@ -309,7 +246,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) AdjustBalance(batch, balanceAdjustments); - this.SetBlockHash(batch, res); + this.SetBlockHash(batch, res, res.Height < indexedHeight); batch.Write(); } From ff88244feb56d421b41c55090e73708ec579c639 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 14 Jun 2022 18:33:28 +1000 Subject: [PATCH 19/42] Add logs --- .../CoinViews/Coindb/BaseCoindb.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs index b6a9dda6fb..d13ba3cbf5 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs @@ -177,7 +177,10 @@ protected void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) } if (indexedTipHash.Height < chainTip.Height) + { + this.logger.LogInformation($"Rewinding the coin database to include missing balance information."); maxHeight = indexedTipHash; + } } var tipHash = GetTipHash(); From c36eb2011fafb3e9c9bc59d16286551359836507 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 14 Jun 2022 20:47:57 +1000 Subject: [PATCH 20/42] Refactor --- .../CoinViews/CachedCoinView.cs | 7 +- .../CoinViews/Coindb/BaseCoindb.cs | 214 ------------ .../Coindb/{LeveldbCoindb.cs => Coindb.cs} | 289 +++++++++++++--- .../CoinViews/Coindb/DBreezeCoindb.cs | 5 +- .../CoinViews/Coindb/ICoindb.cs | 8 + .../CoinViews/Coindb/IDb.cs | 2 + .../CoinViews/Coindb/LevelDb.cs | 8 +- .../CoinViews/Coindb/RocksDb.cs | 8 +- .../CoinViews/Coindb/RocksDbCoindb.cs | 322 ------------------ .../FullNodeBuilderConsensusExtension.cs | 6 +- .../NodeContext.cs | 4 +- 11 files changed, 266 insertions(+), 607 deletions(-) delete mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs rename src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/{LeveldbCoindb.cs => Coindb.cs} (55%) delete mode 100644 src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index a1812c3a52..ff1771fdd5 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -5,6 +5,7 @@ using System.Threading; using Microsoft.Extensions.Logging; using NBitcoin; +using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Consensus.Rules; @@ -136,13 +137,14 @@ public long GetScriptSize private readonly IBlockStore blockStore; private readonly CancellationTokenSource cancellationToken; private readonly ConsensusSettings consensusSettings; + private readonly bool addressIndexingEnabled; private CachePerformanceSnapshot latestPerformanceSnapShot; private IScriptAddressReader scriptAddressReader; private readonly Random random; public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, INodeStats nodeStats, ConsensusSettings consensusSettings, - StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null, INodeLifetime nodeLifetime = null) + StakeChainStore stakeChainStore = null, IRewindDataIndexCache rewindDataIndexCache = null, IScriptAddressReader scriptAddressReader = null, IBlockStore blockStore = null, INodeLifetime nodeLifetime = null, NodeSettings nodeSettings = null) { Guard.NotNull(coindb, nameof(CachedCoinView.coindb)); @@ -162,6 +164,7 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, this.lastCacheFlushTime = this.dateTimeProvider.GetUtcNow(); this.cachedRewindData = new Dictionary(); this.scriptAddressReader = scriptAddressReader; + this.addressIndexingEnabled = nodeSettings?.ConfigReader.GetOrDefault("addressindex", false) ?? false; this.random = new Random(); this.MaxCacheSizeBytes = consensusSettings.MaxCoindbCacheInMB * 1024 * 1024; @@ -173,7 +176,7 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) { - this.coindb.Initialize(chainTip, this.network.Consensus.IsProofOfStake); + this.coindb.Initialize(chainTip, this.addressIndexingEnabled); HashHeightPair coinViewTip = this.coindb.GetTipHash(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs deleted file mode 100644 index d13ba3cbf5..0000000000 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/BaseCoindb.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Logging; -using NBitcoin; -using Stratis.Bitcoin.Interfaces; -using Stratis.Bitcoin.Utilities; - -namespace Stratis.Bitcoin.Features.Consensus.CoinViews -{ - /// - /// A base class for and implementations related to other database types. - /// - public class BaseCoindb - { - protected static readonly byte coinsTable = 1; - protected static readonly byte blockTable = 2; - protected static readonly byte rewindTable = 3; - protected static readonly byte stakeTable = 4; - protected static readonly byte balanceTable = 5; - protected static readonly byte balanceAdjustmentTable = 6; - - /// Database key under which the block hash of the coin view's current tip is stored. - protected static readonly byte[] blockHashKey = new byte[0]; - - /// Database key under which the block hash of the coin view's last indexed tip is stored. - protected static readonly byte[] blockIndexedHashKey = new byte[1]; - - /// Instance logger. - protected ILogger logger; - - public bool BalanceIndexingEnabled { get; protected set; } - - /// Access to dBreeze database. - protected IDb coinDb; - - /// Hash of the block which is currently the tip of the coinview. - protected HashHeightPair persistedCoinviewTip; - - private readonly IScriptAddressReader scriptAddressReader; - - private readonly Network network; - - public BaseCoindb(Network network, IScriptAddressReader scriptAddressReader) - { - this.network = network; - this.scriptAddressReader = scriptAddressReader; - } - - public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) - { - long balance = 0; - { - byte[] row = this.coinDb.Get(balanceTable, txDestination.ToBytes()); - balance = (row == null) ? 0 : BitConverter.ToInt64(row); - } - - foreach ((byte[] key, byte[] value) in this.coinDb.GetAll(balanceAdjustmentTable, ascending: false, - lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), - includeLastKey: false, - firstKey: txDestination.ToBytes(), - includeFirstKey: false)) - { - yield return (BitConverter.ToUInt32(key.Reverse().ToArray()), balance); - balance -= BitConverter.ToInt64(value); - } - - yield return (0, balance); - } - - /// - /// The 'skipMissing' flag allows us to rewind coind db's that have incomplete balance information. - /// - protected void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates) - { - foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) - { - long totalAdjustment = 0; - - foreach (uint height in balanceAdjustments.Keys.OrderBy(k => k)) - { - var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); - byte[] row = this.coinDb.Get(balanceAdjustmentTable, key); - long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + balanceAdjustments[height]; - batch.Put(balanceAdjustmentTable, key, BitConverter.GetBytes(balance)); - - totalAdjustment += balance; - } - - { - var key = txDestination.ToBytes(); - byte[] row = this.coinDb.Get(balanceTable, key); - long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + totalAdjustment; - batch.Put(balanceTable, key, BitConverter.GetBytes(balance)); - } - } - } - - protected void Update(Dictionary> balanceAdjustments, Script scriptPubKey, uint height, long change) - { - if (scriptPubKey.Length == 0 || change == 0) - return; - - foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) - { - if (!balanceAdjustments.TryGetValue(txDestination, out Dictionary value)) - { - value = new Dictionary(); - balanceAdjustments[txDestination] = value; - } - - if (!value.TryGetValue(height, out long balance)) - balance = change; - else - balance += change; - - value[height] = balance; - } - } - - public HashHeightPair GetTipHash() - { - if (this.persistedCoinviewTip == null) - { - var row = this.coinDb.Get(blockTable, blockHashKey); - if (row != null) - { - this.persistedCoinviewTip = new HashHeightPair(); - this.persistedCoinviewTip.FromBytes(row); - } - } - - return this.persistedCoinviewTip; - } - - protected HashHeightPair GetIndexedTipHash() - { - var row = this.coinDb.Get(blockTable, blockIndexedHashKey); - if (row != null) - { - var tip = new HashHeightPair(); - tip.FromBytes(row); - return tip; - } - - return null; - } - - protected void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool forceUpdateIndexedHeight = false) - { - this.persistedCoinviewTip = nextBlockHash; - batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); - if (this.BalanceIndexingEnabled || forceUpdateIndexedHeight) - batch.Put(blockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); - } - - protected virtual HashHeightPair RewindInternal(int startHeight, HashHeightPair target) - { - throw new NotImplementedException(); - } - - protected void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) - { - this.logger.LogInformation("Checking coin database integrity..."); - - HashHeightPair maxHeight = new HashHeightPair(chainTip); - - // If the balance table is empty then rebuild the coin db. - if (this.BalanceIndexingEnabled) - { - HashHeightPair indexedTipHash = this.GetIndexedTipHash(); - if (indexedTipHash == null) - { - this.logger.LogInformation($"Rebuilding coin database to include balance information."); - this.coinDb.Clear(); - return; - } - - if (indexedTipHash.Height < chainTip.Height) - { - this.logger.LogInformation($"Rewinding the coin database to include missing balance information."); - maxHeight = indexedTipHash; - } - } - - var tipHash = GetTipHash(); - - var heightToCheck = chainTip.Height; - - if (heightToCheck > tipHash.Height) - heightToCheck = tipHash.Height; - - // Find the height up to where rewind data is stored above chain tip. - do - { - heightToCheck += 1; - - byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); - if (row == null) - break; - } while (true); - - for (int height = heightToCheck - 1; height > maxHeight.Height;) - { - this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); - - // Do a batch of rewinding. - height = RewindInternal(height, maxHeight).Height; - } - - this.logger.LogInformation("Coin database integrity good."); - } - } -} diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs similarity index 55% rename from src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs rename to src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index f4c7f6679a..60b0ff0b6c 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LeveldbCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -12,10 +12,37 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { /// - /// Persistent implementation of coinview using the LevelDb database engine. + /// Persistent implementation of coinview using an database engine. /// - public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable + /// A database supporting the interface. + public class Coindb : ICoindb, IStakedb, IDisposable where T : IDb, new() { + public const byte CoinsTable = 1; + public const byte BlockTable = 2; + public const byte RewindTable = 3; + public const byte StakeTable = 4; + public const byte BalanceTable = 5; + public const byte BalanceAdjustmentTable = 6; + + /// Database key under which the block hash of the coin view's current tip is stored. + private static readonly byte[] blockHashKey = new byte[0]; + + /// Database key under which the block hash of the coin view's last indexed tip is stored. + private static readonly byte[] blockIndexedHashKey = new byte[1]; + + /// Instance logger. + private ILogger logger; + + public bool BalanceIndexingEnabled { get; private set; } + + /// Access to dBreeze database. + private IDb coinDb; + + /// Hash of the block which is currently the tip of the coinview. + private HashHeightPair persistedCoinviewTip; + + private readonly IScriptAddressReader scriptAddressReader; + private readonly string dataFolder; /// Specification of the network the node runs on - regtest/testnet/mainnet. @@ -30,32 +57,51 @@ public class LevelDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable private const int MaxRewindBatchSize = 10000; - public LevelDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) - : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) - { - } - - public LevelDbCoindb(Network network, string dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : base(network, scriptAddressReader) + public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) { Guard.NotNull(network, nameof(network)); - Guard.NotEmpty(dataFolder, nameof(dataFolder)); + Guard.NotNull(dataFolder, nameof(dataFolder)); - this.dataFolder = dataFolder; + this.dataFolder = dataFolder.CoindbPath; this.dBreezeSerializer = dBreezeSerializer; this.logger = LogManager.GetCurrentClassLogger(); this.network = network; this.performanceCounter = new BackendPerformanceCounter(dateTimeProvider); + this.scriptAddressReader = scriptAddressReader; if (nodeStats.DisplayBenchStats) nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); } + /// + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + long balance; + { + byte[] row = this.coinDb.Get(BalanceTable, txDestination.ToBytes()); + balance = (row == null) ? 0 : BitConverter.ToInt64(row); + } + + foreach ((byte[] key, byte[] value) in this.coinDb.GetAll(BalanceAdjustmentTable, ascending: false, + lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), + includeLastKey: false, + firstKey: txDestination.ToBytes(), + includeFirstKey: false)) + { + yield return (BitConverter.ToUInt32(key.Reverse().ToArray()), balance); + balance -= BitConverter.ToInt64(value); + } + + yield return (0, balance); + } + + /// public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) { // Open a connection to a new DB and create if not found - this.coinDb = new LevelDb(this.dataFolder); + this.coinDb = new T(); + this.coinDb.Open(this.dataFolder); + this.BalanceIndexingEnabled = balanceIndexingEnabled; EnsureCoinDatabaseIntegrity(chainTip); @@ -74,6 +120,7 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); } + /// public FetchCoinsResponse FetchCoins(OutPoint[] utxos) { FetchCoinsResponse res = new FetchCoinsResponse(); @@ -84,7 +131,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) foreach (OutPoint outPoint in utxos) { - byte[] row = this.coinDb.Get(coinsTable, outPoint.ToBytes()); + byte[] row = this.coinDb.Get(CoinsTable, outPoint.ToBytes()); Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); @@ -96,6 +143,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) return res; } + /// public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { int insertedEntities = 0; @@ -121,7 +169,7 @@ public void SaveChanges(IList unspentOutputs, Dictionary unspentOutputs, Dictionary unspentOutputs, Dictionary unspentOutputs, Dictionary public int GetMinRewindHeight() { // Find the first row with a rewind table key prefix. - var res = this.coinDb.GetAll(rewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); + var res = this.coinDb.GetAll(RewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); if (res == default || res.Item1.Length != 5) return -1; @@ -177,9 +226,79 @@ public HashHeightPair Rewind(HashHeightPair target) return RewindInternal(current.Height, target); } + /// + public RewindData GetRewindData(int height) + { + byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); + return row != null ? this.dBreezeSerializer.Deserialize(row) : null; + } + + /// + public void PutStake(IEnumerable stakeEntries) + { + using (var batch = this.coinDb.GetWriteBatch()) + { + foreach (StakeItem stakeEntry in stakeEntries) + { + if (!stakeEntry.InStore) + { + batch.Put(StakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); + stakeEntry.InStore = true; + } + } + + batch.Write(); + } + } + + /// + public void GetStake(IEnumerable blocklist) + { + foreach (StakeItem blockStake in blocklist) + { + this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); + byte[] stakeRow = this.coinDb.Get(StakeTable, blockStake.BlockId.ToBytes(false)); + + if (stakeRow != null) + { + blockStake.BlockStake = this.dBreezeSerializer.Deserialize(stakeRow); + blockStake.InStore = true; + } + } + } + + /// + public HashHeightPair GetTipHash() + { + if (this.persistedCoinviewTip == null) + { + var row = this.coinDb.Get(BlockTable, blockHashKey); + if (row != null) + { + this.persistedCoinviewTip = new HashHeightPair(); + this.persistedCoinviewTip.FromBytes(row); + } + } + + return this.persistedCoinviewTip; + } + + private HashHeightPair GetIndexedTipHash() + { + var row = this.coinDb.Get(BlockTable, blockIndexedHashKey); + if (row != null) + { + var tip = new HashHeightPair(); + tip.FromBytes(row); + return tip; + } + + return null; + } + private bool TryGetCoins(byte[] key, out Coins coins) { - byte[] row2 = this.coinDb.Get(coinsTable, key); + byte[] row2 = this.coinDb.Get(CoinsTable, key); if (row2 == null) { coins = null; @@ -191,7 +310,7 @@ private bool TryGetCoins(byte[] key, out Coins coins) return true; } - protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair target) + private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) { HashHeightPair res = null; @@ -204,12 +323,12 @@ protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) { byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); - byte[] row = this.coinDb.Get(rewindTable, rowKey); + byte[] row = this.coinDb.Get(RewindTable, rowKey); if (row == null) throw new InvalidOperationException($"No rewind data found for block at height {height}."); - batch.Delete(rewindTable, rowKey); + batch.Delete(RewindTable, rowKey); var rewindData = this.dBreezeSerializer.Deserialize(row); @@ -223,7 +342,7 @@ protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair if (height <= indexedHeight) Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); - batch.Delete(coinsTable, key); + batch.Delete(CoinsTable, key); } else { @@ -234,7 +353,7 @@ protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) { this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); - batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); + batch.Put(CoinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); if (height <= indexedHeight) Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); @@ -252,55 +371,65 @@ protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair return res; } - public RewindData GetRewindData(int height) + + private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool forceUpdateIndexedHeight = false) { - byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); - return row != null ? this.dBreezeSerializer.Deserialize(row) : null; + this.persistedCoinviewTip = nextBlockHash; + batch.Put(BlockTable, blockHashKey, nextBlockHash.ToBytes()); + if (this.BalanceIndexingEnabled || forceUpdateIndexedHeight) + batch.Put(BlockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); } - /// - /// Persists unsaved POS blocks information to the database. - /// - /// List of POS block information to be examined and persists if unsaved. - public void PutStake(IEnumerable stakeEntries) + private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) { - using (var batch = this.coinDb.GetWriteBatch()) + this.logger.LogInformation("Checking coin database integrity..."); + + HashHeightPair maxHeight = new HashHeightPair(chainTip); + + // If the balance table is empty then rebuild the coin db. + if (this.BalanceIndexingEnabled) { - foreach (StakeItem stakeEntry in stakeEntries) + HashHeightPair indexedTipHash = this.GetIndexedTipHash(); + if (indexedTipHash == null) { - if (!stakeEntry.InStore) - { - batch.Put(stakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); - stakeEntry.InStore = true; - } + this.logger.LogInformation($"Rebuilding coin database to include balance information."); + this.coinDb.Clear(); + return; } - batch.Write(); + if (indexedTipHash.Height < chainTip.Height) + { + this.logger.LogInformation($"Rewinding the coin database to include missing balance information."); + maxHeight = indexedTipHash; + } } - } - /// - /// Retrieves POS blocks information from the database. - /// - /// List of partially initialized POS block information that is to be fully initialized with the values from the database. - public void GetStake(IEnumerable blocklist) - { - foreach (StakeItem blockStake in blocklist) + var heightToCheck = chainTip.Height; + + // Find the height up to where rewind data is stored above chain tip. + do { - this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.coinDb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); + heightToCheck += 1; - if (stakeRow != null) - { - blockStake.BlockStake = this.dBreezeSerializer.Deserialize(stakeRow); - blockStake.InStore = true; - } + byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); + if (row == null) + break; + } while (true); + + for (int height = heightToCheck - 1; height > maxHeight.Height;) + { + this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); + + // Do a batch of rewinding. + height = RewindInternal(height, maxHeight).Height; } + + this.logger.LogInformation("Coin database integrity good."); } private void AddBenchStats(StringBuilder log) { - log.AppendLine(">> Leveldb Bench"); + log.AppendLine(">> Coindb Bench"); BackendPerformanceSnapshot snapShot = this.performanceCounter.Snapshot(); @@ -312,6 +441,54 @@ private void AddBenchStats(StringBuilder log) this.latestPerformanceSnapShot = snapShot; } + + private void AdjustBalance(IDbBatch batch, Dictionary> balanceUpdates) + { + foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) + { + long totalAdjustment = 0; + + foreach (uint height in balanceAdjustments.Keys.OrderBy(k => k)) + { + var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); + byte[] row = this.coinDb.Get(BalanceAdjustmentTable, key); + long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + balanceAdjustments[height]; + batch.Put(BalanceAdjustmentTable, key, BitConverter.GetBytes(balance)); + + totalAdjustment += balance; + } + + { + var key = txDestination.ToBytes(); + byte[] row = this.coinDb.Get(BalanceTable, key); + long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + totalAdjustment; + batch.Put(BalanceTable, key, BitConverter.GetBytes(balance)); + } + } + } + + private void Update(Dictionary> balanceAdjustments, Script scriptPubKey, uint height, long change) + { + if (scriptPubKey.Length == 0 || change == 0) + return; + + foreach (TxDestination txDestination in this.scriptAddressReader.GetDestinationFromScriptPubKey(this.network, scriptPubKey)) + { + if (!balanceAdjustments.TryGetValue(txDestination, out Dictionary value)) + { + value = new Dictionary(); + balanceAdjustments[txDestination] = value; + } + + if (!value.TryGetValue(height, out long balance)) + balance = change; + else + balance += change; + + value[height] = balance; + } + } + /// public void Dispose() { diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs index 3b94224034..18f0e32373 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs @@ -309,10 +309,7 @@ public HashHeightPair Rewind(HashHeightPair target) return res; } - /// - /// Persists unsaved POS blocks information to the database. - /// - /// List of POS block information to be examined and persists if unsaved. + /// public void PutStake(IEnumerable stakeEntries) { using (DBreeze.Transactions.Transaction transaction = this.CreateTransaction()) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index bc7b9574a6..bf64e20d19 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -93,8 +93,16 @@ public interface ICoindb public interface IStakedb : ICoindb { + /// + /// Persists unsaved POS blocks information to the database. + /// + /// List of POS block information to be examined and persists if unsaved. void PutStake(IEnumerable stakeEntries); + /// + /// Retrieves POS blocks information from the database. + /// + /// List of partially initialized POS block information that is to be fully initialized with the values from the database. void GetStake(IEnumerable blocklist); } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index d20363eb2f..c87712a9de 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -5,6 +5,8 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { public interface IDb : IDisposable { + void Open(string name); + byte[] Get(byte table, byte[] key); IEnumerable<(byte[], byte[])> GetAll(byte table, bool keysOnly = false, bool ascending = true, diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index 46fe4d283e..a939e4174d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -35,11 +35,15 @@ public class LevelDb : IDb { private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); - private readonly string name; + private string name; DB db; - public LevelDb(string name) + public LevelDb() + { + } + + public void Open(string name) { this.name = name; this.db = new DB(new Options() { CreateIfMissing = true }, name); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs index 0b37d34dda..207b7e05a3 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs @@ -35,11 +35,15 @@ public class RocksDb : IDb { private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); - private readonly string name; + private string name; RocksDbSharp.RocksDb db; - public RocksDb(string name) + public RocksDb() + { + } + + public void Open(string name) { this.name = name; this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), name); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs deleted file mode 100644 index 934c034ae9..0000000000 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDbCoindb.cs +++ /dev/null @@ -1,322 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Extensions.Logging; -using NBitcoin; -using Stratis.Bitcoin.Configuration; -using Stratis.Bitcoin.Configuration.Logging; -using Stratis.Bitcoin.Interfaces; -using Stratis.Bitcoin.Utilities; - -namespace Stratis.Bitcoin.Features.Consensus.CoinViews -{ - /// - /// Persistent implementation of coinview using the RocksDb database engine. - /// - public class RocksDbCoindb : BaseCoindb, ICoindb, IStakedb, IDisposable - { - private readonly string dataFolder; - - /// Specification of the network the node runs on - regtest/testnet/mainnet. - private readonly Network network; - - /// Performance counter to measure performance of the database insert and query operations. - private readonly BackendPerformanceCounter performanceCounter; - - private BackendPerformanceSnapshot latestPerformanceSnapShot; - - private readonly DBreezeSerializer dBreezeSerializer; - - private const int MaxRewindBatchSize = 10000; - - public RocksDbCoindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) - : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) - { - } - - public RocksDbCoindb(Network network, string dataFolder, IDateTimeProvider dateTimeProvider, - INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) : base(network, scriptAddressReader) - { - Guard.NotNull(network, nameof(network)); - Guard.NotEmpty(dataFolder, nameof(dataFolder)); - - this.dataFolder = dataFolder; - this.dBreezeSerializer = dBreezeSerializer; - this.logger = LogManager.GetCurrentClassLogger(); - this.network = network; - this.performanceCounter = new BackendPerformanceCounter(dateTimeProvider); - - if (nodeStats.DisplayBenchStats) - nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); - } - - public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) - { - // Open a connection to a new DB and create if not found - this.coinDb = new RocksDb(this.dataFolder); - this.BalanceIndexingEnabled = balanceIndexingEnabled; - - EnsureCoinDatabaseIntegrity(chainTip); - - Block genesis = this.network.GetGenesis(); - - if (this.GetTipHash() == null) - { - using (var batch = this.coinDb.GetWriteBatch()) - { - this.SetBlockHash(batch, new HashHeightPair(genesis.GetHash(), 0)); - batch.Write(); - } - } - - this.logger.LogInformation("Coinview initialized with tip '{0}'.", this.persistedCoinviewTip); - } - - public FetchCoinsResponse FetchCoins(OutPoint[] utxos) - { - FetchCoinsResponse res = new FetchCoinsResponse(); - - using (new StopwatchDisposable(o => this.performanceCounter.AddQueryTime(o))) - { - this.performanceCounter.AddQueriedEntities(utxos.Length); - - foreach (OutPoint outPoint in utxos) - { - byte[] row = this.coinDb.Get(coinsTable, outPoint.ToBytes()); - Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; - - this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); - - res.UnspentOutputs.Add(outPoint, new UnspentOutput(outPoint, outputs)); - } - } - - return res; - } - - public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) - { - int insertedEntities = 0; - - using (var batch = this.coinDb.GetWriteBatch()) - { - if (this.BalanceIndexingEnabled) - this.AdjustBalance(batch, balanceUpdates); - - using (new StopwatchDisposable(o => this.performanceCounter.AddInsertTime(o))) - { - HashHeightPair current = this.GetTipHash(); - if (current != oldBlockHash) - { - this.logger.LogTrace("(-)[BLOCKHASH_MISMATCH]"); - throw new InvalidOperationException("Invalid oldBlockHash"); - } - - // Here we'll add items to be inserted in a second pass. - List toInsert = new List(); - - foreach (var coin in unspentOutputs.OrderBy(utxo => utxo.OutPoint, new OutPointComparer())) - { - if (coin.Coins == null) - { - this.logger.LogDebug("Outputs of transaction ID '{0}' are prunable and will be removed from the database.", coin.OutPoint); - batch.Delete(coinsTable, coin.OutPoint.ToBytes()); - } - else - { - // Add the item to another list that will be used in the second pass. - // This is for performance reasons: dBreeze is optimized to run the same kind of operations, sorted. - toInsert.Add(coin); - } - } - - for (int i = 0; i < toInsert.Count; i++) - { - var coin = toInsert[i]; - this.logger.LogDebug("Outputs of transaction ID '{0}' are NOT PRUNABLE and will be inserted into the database. {1}/{2}.", coin.OutPoint, i, toInsert.Count); - - batch.Put(coinsTable, coin.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(coin.Coins)); - } - - if (rewindDataList != null) - { - foreach (RewindData rewindData in rewindDataList) - { - var nextRewindIndex = rewindData.PreviousBlockHash.Height + 1; - - this.logger.LogDebug("Rewind state #{0} created.", nextRewindIndex); - - batch.Put(rewindTable, BitConverter.GetBytes(nextRewindIndex).Reverse().ToArray(), this.dBreezeSerializer.Serialize(rewindData)); - } - } - - insertedEntities += unspentOutputs.Count; - this.SetBlockHash(batch, nextBlockHash); - batch.Write(); - } - } - - this.performanceCounter.AddInsertedEntities(insertedEntities); - } - - public int GetMinRewindHeight() - { - // Find the first row with a rewind table key prefix. - var res = this.coinDb.GetAll(rewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); - if (res == default || res.Item1.Length != 5) - return -1; - - return BitConverter.ToInt32(res.Item1.SafeSubarray(0, 4).Reverse().ToArray()); - } - - /// - public HashHeightPair Rewind(HashHeightPair target) - { - HashHeightPair current = this.GetTipHash(); - return RewindInternal(current.Height, target); - } - - private bool TryGetCoins(byte[] key, out Coins coins) - { - byte[] row2 = this.coinDb.Get(coinsTable, key); - if (row2 == null) - { - coins = null; - return false; - } - - coins = this.dBreezeSerializer.Deserialize(row2); - - return true; - } - - protected override HashHeightPair RewindInternal(int startHeight, HashHeightPair target) - { - HashHeightPair res = null; - - int indexedHeight = this.GetIndexedTipHash()?.Height ?? -1; - - using (var batch = this.coinDb.GetWriteBatch()) - { - var balanceAdjustments = new Dictionary>(); - - for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) - { - byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); - byte[] row = this.coinDb.Get(rewindTable, rowKey); - - if (row == null) - throw new InvalidOperationException($"No rewind data found for block at height {height}."); - - batch.Delete(rewindTable, rowKey); - - var rewindData = this.dBreezeSerializer.Deserialize(row); - - foreach (OutPoint outPoint in rewindData.OutputsToRemove) - { - byte[] key = outPoint.ToBytes(); - if (this.TryGetCoins(key, out Coins coins)) - { - this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); - - if (height <= indexedHeight) - Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); - - batch.Delete(coinsTable, key); - } - else - { - throw new InvalidOperationException(string.Format("Outputs of outpoint '{0}' were not found when attempting removal.", outPoint)); - } - } - - foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) - { - this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); - batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); - - if (height <= indexedHeight) - Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); - } - - res = rewindData.PreviousBlockHash; - } - - AdjustBalance(batch, balanceAdjustments); - - this.SetBlockHash(batch, res, res.Height < indexedHeight); - batch.Write(); - } - - return res; - } - - public RewindData GetRewindData(int height) - { - byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); - return row != null ? this.dBreezeSerializer.Deserialize(row) : null; - } - - /// - /// Persists unsaved POS blocks information to the database. - /// - /// List of POS block information to be examined and persists if unsaved. - public void PutStake(IEnumerable stakeEntries) - { - using (var batch = this.coinDb.GetWriteBatch()) - { - foreach (StakeItem stakeEntry in stakeEntries) - { - if (!stakeEntry.InStore) - { - batch.Put(stakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); - stakeEntry.InStore = true; - } - } - - batch.Write(); - } - } - - /// - /// Retrieves POS blocks information from the database. - /// - /// List of partially initialized POS block information that is to be fully initialized with the values from the database. - public void GetStake(IEnumerable blocklist) - { - foreach (StakeItem blockStake in blocklist) - { - this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.coinDb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); - - if (stakeRow != null) - { - blockStake.BlockStake = this.dBreezeSerializer.Deserialize(stakeRow); - blockStake.InStore = true; - } - } - } - - private void AddBenchStats(StringBuilder log) - { - log.AppendLine(">> RocksDb Bench"); - - BackendPerformanceSnapshot snapShot = this.performanceCounter.Snapshot(); - - if (this.latestPerformanceSnapShot == null) - log.AppendLine(snapShot.ToString()); - else - log.AppendLine((snapShot - this.latestPerformanceSnapShot).ToString()); - - this.latestPerformanceSnapShot = snapShot; - } - - /// - public void Dispose() - { - this.coinDb.Dispose(); - } - } -} \ No newline at end of file diff --git a/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs b/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs index fb3f89f155..17b073e75e 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs @@ -87,15 +87,15 @@ public static void ConfigureCoinDatabaseImplementation(this IServiceCollection s break; case DbType.Leveldb: - services.AddSingleton(); + services.AddSingleton>(); break; case DbType.RocksDb: - services.AddSingleton(); + services.AddSingleton>(); break; default: - services.AddSingleton(); + services.AddSingleton>(); break; } } diff --git a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs index 055a6a0cd3..cb31b64b78 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs @@ -28,7 +28,7 @@ public NodeContext(object caller, string name, Network network) this.FolderName = TestBase.CreateTestDir(caller, name); var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); - this.Coindb = new LevelDbCoindb(network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer, new ScriptAddressReader()); + this.Coindb = new Coindb(network, new DataFolder(this.FolderName), dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer, new ScriptAddressReader()); this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0), false); this.cleanList = new List { (IDisposable)this.Coindb }; } @@ -66,7 +66,7 @@ public void ReloadPersistentCoinView(ChainedHeader chainTip) this.cleanList.Remove((IDisposable)this.Coindb); var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); - this.Coindb = new LevelDbCoindb(this.Network, this.FolderName, dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer, new ScriptAddressReader()); + this.Coindb = new Coindb(this.Network, new DataFolder(this.FolderName), dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer, new ScriptAddressReader()); this.Coindb.Initialize(chainTip, false); this.cleanList.Add((IDisposable)this.Coindb); From 1e4795149d4694ea3455150488c4b50f67853cf1 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Wed, 15 Jun 2022 00:57:33 +1000 Subject: [PATCH 21/42] Add sync method --- .../AddressIndexing/AddressIndexerCV.cs | 17 +------ .../CoinViews/CachedCoinView.cs | 48 +++++++++++++------ .../CoinViews/CoinView.cs | 6 +++ .../CoinViews/Coindb/Coindb.cs | 33 ++++++------- .../CoinViews/Coindb/DBreezeCoindb.cs | 2 +- .../CoinViews/Coindb/ICoindb.cs | 4 +- .../CoinViews/InMemoryCoinView.cs | 8 +++- .../Rules/CommonRules/LoadCoinviewRule.cs | 1 + .../MemPoolCoinView.cs | 5 ++ .../NodeContext.cs | 4 +- 10 files changed, 74 insertions(+), 54 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs index 5d57dc482c..b7bfd39893 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs @@ -7,7 +7,6 @@ using Stratis.Bitcoin.Features.BlockStore.Models; using Stratis.Bitcoin.Features.Consensus.CoinViews; using Stratis.Bitcoin.Interfaces; -using Stratis.Bitcoin.Utilities; namespace Stratis.Bitcoin.Features.BlockStore.AddressIndexing { @@ -32,21 +31,9 @@ public AddressIndexerCV(Network network, ChainIndexer chainIndexer, IScriptAddre private ChainedHeader GetTip() { - HashHeightPair coinViewTip = this.coinView.GetTipHash(); - if (coinViewTip.Hash == this.chainIndexer.Tip.HashBlock) - return this.chainIndexer.Tip; + this.coinView.Sync(this.chainIndexer); - ChainedHeader fork = this.chainIndexer[coinViewTip.Hash]; - if (fork == null) - { - // Determine the last usable height. - int height = BinarySearch.BinaryFindFirst(h => this.coinView.GetRewindData(h).PreviousBlockHash.Hash != this.chainIndexer[h].Previous.HashBlock, 0, Math.Min(this.chainIndexer.Height, coinViewTip.Height)); - fork = this.chainIndexer[(height < 0) ? 0 : this.coinView.GetRewindData(height).PreviousBlockHash.Hash]; - } - - this.coinView.Rewind(new HashHeightPair(fork)); - - return fork; + return this.chainIndexer[this.coinView.GetTipHash().Hash]; } public void Initialize() diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index ff1771fdd5..a116a4a8a3 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -174,26 +174,46 @@ public CachedCoinView(Network network, ICheckpoints checkpoints, ICoindb coindb, nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); } - public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + /// + /// Remain on-chain. + /// + public void Sync(ChainIndexer chainIndexer) { - this.coindb.Initialize(chainTip, this.addressIndexingEnabled); - - HashHeightPair coinViewTip = this.coindb.GetTipHash(); - - while (true) + lock (this.lockobj) { - ChainedHeader pendingTip = chainTip.FindAncestorOrSelf(coinViewTip.Hash); + HashHeightPair coinViewTip = this.GetTipHash(); + if (coinViewTip.Hash == chainIndexer.Tip.HashBlock) + return; - if (pendingTip != null) - break; + Flush(); - if ((coinViewTip.Height % 100) == 0) - this.logger.LogInformation("Rewinding coin view from '{0}' to {1}.", coinViewTip, chainTip); + ChainedHeader fork = chainIndexer[coinViewTip.Hash]; + if (fork == null) + { + // Determine the last usable height. + int height = BinarySearch.BinaryFindFirst(h => this.GetRewindData(h).PreviousBlockHash.Hash != chainIndexer[h].Previous.HashBlock, 0, Math.Min(chainIndexer.Height, coinViewTip.Height)); + fork = chainIndexer[(height < 0) ? 0 : this.GetRewindData(height).PreviousBlockHash.Hash]; + } - // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip. - // The node will complete loading before connecting to peers so the chain will never know that a reorg happened. - coinViewTip = this.coindb.Rewind(new HashHeightPair(chainTip)); + do + { + if ((coinViewTip.Height % 100) == 0) + this.logger.LogInformation("Rewinding coin view from '{0}' to {1}.", coinViewTip, fork); + + // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip. + // The node will complete loading before connecting to peers so the chain will never know that a reorg happened. + coinViewTip = this.coindb.Rewind(new HashHeightPair(fork)); + } while (coinViewTip.Height != fork.Height); } + } + + public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, ConsensusRulesContainer consensusRulesContainer) + { + this.coindb.Initialize(this.addressIndexingEnabled); + + Sync(chainIndexer); + + HashHeightPair coinViewTip = this.coindb.GetTipHash(); // If the coin view is behind the block store then catch up from the block store. if (coinViewTip.Height < chainTip.Height) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs index 55c7054432..4798a14e09 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinView.cs @@ -42,6 +42,12 @@ public interface ICoinView /// List of rewind data items to be persisted. This should only be used when calling . void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null); + /// + /// Brings the coinview back on-chain if a re-org occurred. + /// + /// The current consensus chain. + void Sync(ChainIndexer chainIndexer); + /// /// Obtains information about unspent outputs. /// diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 60b0ff0b6c..85b85ae014 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -96,7 +96,7 @@ public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTime } /// - public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) + public void Initialize(bool balanceIndexingEnabled) { // Open a connection to a new DB and create if not found this.coinDb = new T(); @@ -104,7 +104,7 @@ public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) this.BalanceIndexingEnabled = balanceIndexingEnabled; - EnsureCoinDatabaseIntegrity(chainTip); + EnsureCoinDatabaseIntegrity(); Block genesis = this.network.GetGenesis(); @@ -380,11 +380,18 @@ private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool for batch.Put(BlockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); } - private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) + private void EnsureCoinDatabaseIntegrity() { this.logger.LogInformation("Checking coin database integrity..."); - HashHeightPair maxHeight = new HashHeightPair(chainTip); + if (this.GetTipHash() == null) + { + this.logger.LogInformation($"Rebuilding coin database that has no tip information."); + this.coinDb.Clear(); + return; + } + + HashHeightPair maxHeight = new HashHeightPair(this.persistedCoinviewTip.Hash, this.persistedCoinviewTip.Height); // If the balance table is empty then rebuild the coin db. if (this.BalanceIndexingEnabled) @@ -397,28 +404,16 @@ private void EnsureCoinDatabaseIntegrity(ChainedHeader chainTip) return; } - if (indexedTipHash.Height < chainTip.Height) + if (indexedTipHash.Height < maxHeight.Height) { this.logger.LogInformation($"Rewinding the coin database to include missing balance information."); maxHeight = indexedTipHash; } } - var heightToCheck = chainTip.Height; - - // Find the height up to where rewind data is stored above chain tip. - do - { - heightToCheck += 1; - - byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(heightToCheck).Reverse().ToArray()); - if (row == null) - break; - } while (true); - - for (int height = heightToCheck - 1; height > maxHeight.Height;) + for (int height = this.persistedCoinviewTip.Height; height > maxHeight.Height;) { - this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{chainTip}'."); + this.logger.LogInformation($"Fixing coin database, deleting rewind data at height {height} above tip '{maxHeight}'."); // Do a batch of rewinding. height = RewindInternal(height, maxHeight).Height; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs index 18f0e32373..fb5c3aa95d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/DBreezeCoindb.cs @@ -67,7 +67,7 @@ public DBreezeCoindb(Network network, string folder, IDateTimeProvider dateTimeP nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 300); } - public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) + public void Initialize(bool balanceIndexingEnabled) { Block genesis = this.network.GetGenesis(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index bf64e20d19..f16f14723f 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -10,8 +10,8 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews public interface ICoindb { /// Initialize the coin database. - /// The current chain's tip. - void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled); + /// Indicates whether to enable balance indexing. + void Initialize(bool balanceIndexingEnabled); bool BalanceIndexingEnabled { get; } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs index 25c325d0d8..e0d8598ed0 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/InMemoryCoinView.cs @@ -41,7 +41,13 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen throw new NotImplementedException(); } - public void Initialize(ChainedHeader chainTip, bool balanceIndexingEnabled) + /// + public void Initialize(bool balanceIndexingEnabled) + { + } + + /// + public void Sync(ChainIndexer chainIndexer) { } diff --git a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs index 670ab47c2c..bf4d5c26b1 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/Rules/CommonRules/LoadCoinviewRule.cs @@ -25,6 +25,7 @@ public override Task RunAsync(RuleContext context) // unless the coinview threshold is reached. this.Logger.LogDebug("Saving coinview changes."); var utxoRuleContext = context as UtxoRuleContext; + this.PowParent.UtxoSet.Sync(this.Parent.ChainIndexer); this.PowParent.UtxoSet.SaveChanges(utxoRuleContext.UnspentOutputSet.GetCoins(), new HashHeightPair(oldBlock), new HashHeightPair(nextBlock)); return Task.CompletedTask; diff --git a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs index da7f97c7e7..10478df0c8 100644 --- a/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs +++ b/src/Stratis.Bitcoin.Features.MemoryPool/MemPoolCoinView.cs @@ -55,6 +55,11 @@ public void Initialize(ChainedHeader chainTip, ChainIndexer chainIndexer, Consen throw new NotImplementedException(); } + /// + public void Sync(ChainIndexer chainIndexer) + { + } + /// /// Gets the unspent transaction output set. /// diff --git a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs index cb31b64b78..0e426c5f9c 100644 --- a/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs +++ b/src/Stratis.Bitcoin.IntegrationTests/NodeContext.cs @@ -29,7 +29,7 @@ public NodeContext(object caller, string name, Network network) var dateTimeProvider = new DateTimeProvider(); var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); this.Coindb = new Coindb(network, new DataFolder(this.FolderName), dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(network), new Mock().Object), serializer, new ScriptAddressReader()); - this.Coindb.Initialize(new ChainedHeader(network.GetGenesis().Header, network.GenesisHash, 0), false); + this.Coindb.Initialize(false); this.cleanList = new List { (IDisposable)this.Coindb }; } @@ -68,7 +68,7 @@ public void ReloadPersistentCoinView(ChainedHeader chainTip) var serializer = new DBreezeSerializer(this.Network.Consensus.ConsensusFactory); this.Coindb = new Coindb(this.Network, new DataFolder(this.FolderName), dateTimeProvider, new NodeStats(dateTimeProvider, NodeSettings.Default(this.Network), new Mock().Object), serializer, new ScriptAddressReader()); - this.Coindb.Initialize(chainTip, false); + this.Coindb.Initialize(false); this.cleanList.Add((IDisposable)this.Coindb); } } From bed331655e46ac2eceedb64843f445b6718819df Mon Sep 17 00:00:00 2001 From: quantumagi Date: Wed, 15 Jun 2022 15:24:41 +1000 Subject: [PATCH 22/42] Add ReadWriteBatch --- .../CoinViews/CachedCoinView.cs | 4 +- .../CoinViews/Coindb/Coindb.cs | 8 +-- .../CoinViews/Coindb/IDb.cs | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index a116a4a8a3..444287eaac 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -195,7 +195,7 @@ public void Sync(ChainIndexer chainIndexer) fork = chainIndexer[(height < 0) ? 0 : this.GetRewindData(height).PreviousBlockHash.Hash]; } - do + while (coinViewTip.Height != fork.Height) { if ((coinViewTip.Height % 100) == 0) this.logger.LogInformation("Rewinding coin view from '{0}' to {1}.", coinViewTip, fork); @@ -203,7 +203,7 @@ public void Sync(ChainIndexer chainIndexer) // If the block store was initialized behind the coin view's tip, rewind it to on or before it's tip. // The node will complete loading before connecting to peers so the chain will never know that a reorg happened. coinViewTip = this.coindb.Rewind(new HashHeightPair(fork)); - } while (coinViewTip.Height != fork.Height); + }; } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 85b85ae014..845a8f10f2 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -296,9 +296,9 @@ private HashHeightPair GetIndexedTipHash() return null; } - private bool TryGetCoins(byte[] key, out Coins coins) + private bool TryGetCoins(ReadWriteBatch readWriteBatch, byte[] key, out Coins coins) { - byte[] row2 = this.coinDb.Get(CoinsTable, key); + byte[] row2 = readWriteBatch.Get(CoinsTable, key); if (row2 == null) { coins = null; @@ -316,7 +316,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) int indexedHeight = this.GetIndexedTipHash()?.Height ?? -1; - using (var batch = this.coinDb.GetWriteBatch()) + using (var batch = this.coinDb.GetReadWriteBatch()) { var balanceAdjustments = new Dictionary>(); @@ -335,7 +335,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) foreach (OutPoint outPoint in rewindData.OutputsToRemove) { byte[] key = outPoint.ToBytes(); - if (this.TryGetCoins(key, out Coins coins)) + if (this.TryGetCoins(batch, key, out Coins coins)) { this.logger.LogDebug("Outputs of outpoint '{0}' will be removed.", outPoint); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index c87712a9de..cfd3de967c 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using NBitcoin; namespace Stratis.Bitcoin.Features.Consensus.CoinViews { @@ -25,4 +27,56 @@ public interface IDbBatch : IDisposable void Write(); } + + public class ReadWriteBatch : IDbBatch + { + private readonly IDb db; + private readonly IDbBatch batch; + private Dictionary cache; + + public ReadWriteBatch(IDb db) + { + this.db = db; + this.batch = db.GetWriteBatch(); + this.cache = new Dictionary(new ByteArrayComparer()); + } + + public IDbBatch Put(byte table, byte[] key, byte[] value) + { + this.cache[new byte[] { table }.Concat(key).ToArray()] = value; + return this.batch.Put(table, key, value); + } + + public IDbBatch Delete(byte table, byte[] key) + { + this.cache[new byte[] { table }.Concat(key).ToArray()] = null; + return this.batch.Delete(table, key); + } + + public byte[] Get(byte table, byte[] key) + { + if (this.cache.TryGetValue(new byte[] { table }.Concat(key).ToArray(), out byte[] value)) + return value; + + return this.db.Get(table, key); + } + + public void Write() + { + this.batch.Write(); + } + + public void Dispose() + { + this.batch.Dispose(); + } + } + + public static class IDbExt + { + public static ReadWriteBatch GetReadWriteBatch(this IDb db) + { + return new ReadWriteBatch(db); + } + } } From 0ba5076382947ab57b79d46974fae0ed8c701720 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Wed, 15 Jun 2022 18:09:55 +1000 Subject: [PATCH 23/42] Fix tests --- .../CoinViews/CoinviewTests.cs | 2 +- src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs index c2212e3f9e..8767ac6cfb 100644 --- a/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs +++ b/src/Stratis.Bitcoin.Features.Consensus.Tests/CoinViews/CoinviewTests.cs @@ -43,7 +43,7 @@ public CoinviewTests() this.nodeStats = new NodeStats(this.dateTimeProvider, NodeSettings.Default(this.network), new Mock().Object); this.coindb = new DBreezeCoindb(this.network, this.dataFolder, this.dateTimeProvider, this.loggerFactory, this.nodeStats, new DBreezeSerializer(this.network.Consensus.ConsensusFactory)); - this.coindb.Initialize(new ChainedHeader(this.network.GetGenesis().Header, this.network.GenesisHash, 0), false); + this.coindb.Initialize(false); this.chainIndexer = new ChainIndexer(this.network); this.stakeChainStore = new StakeChainStore(this.network, this.chainIndexer, (IStakedb)this.coindb, this.loggerFactory); diff --git a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs index 817f338419..1642f736de 100644 --- a/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs +++ b/src/Stratis.Bitcoin.Tests/Consensus/TestInMemoryCoinView.cs @@ -75,6 +75,11 @@ public FetchCoinsResponse FetchCoins(OutPoint[] txIds) throw new NotImplementedException(); } + /// + public void Sync(ChainIndexer chainIndexer) + { + } + /// public void SaveChanges(IList unspentOutputs, HashHeightPair oldBlockHash, HashHeightPair nextBlockHash, List rewindDataList = null) { From 7c38975c22bbb3195a2d68bdbe7437e6980ba97d Mon Sep 17 00:00:00 2001 From: quantumagi Date: Wed, 15 Jun 2022 19:23:38 +1000 Subject: [PATCH 24/42] Bug fix --- .../CoinViews/CachedCoinView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs index 444287eaac..985c6f93f7 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CachedCoinView.cs @@ -191,8 +191,8 @@ public void Sync(ChainIndexer chainIndexer) if (fork == null) { // Determine the last usable height. - int height = BinarySearch.BinaryFindFirst(h => this.GetRewindData(h).PreviousBlockHash.Hash != chainIndexer[h].Previous.HashBlock, 0, Math.Min(chainIndexer.Height, coinViewTip.Height)); - fork = chainIndexer[(height < 0) ? 0 : this.GetRewindData(height).PreviousBlockHash.Hash]; + int height = BinarySearch.BinaryFindFirst(h => (h > chainIndexer.Height) || this.GetRewindData(h).PreviousBlockHash.Hash != chainIndexer[h].Previous.HashBlock, 0, coinViewTip.Height + 1) - 1; + fork = chainIndexer[(height < 0) ? coinViewTip.Height : height]; } while (coinViewTip.Height != fork.Height) From 8b2d80cfc92c7d034b03fbda4d9a90e0df25127c Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 16 Jun 2022 16:42:58 +1000 Subject: [PATCH 25/42] Handle unresolvable destinations --- .../AddressIndexing/AddressIndexerCV.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs index b7bfd39893..7eff99c20d 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs @@ -50,10 +50,13 @@ public AddressBalancesResult GetAddressBalances(string[] addresses, int minConfi { return new AddressBalancesResult() { - Balances = addresses.Select(address => new AddressBalanceResult() + Balances = addresses + .Select(address => (address, destination: AddressToDestination(address))) + .Select(t => new AddressBalanceResult() { - Address = address, - Balance = new Money(this.coinView.GetBalance(AddressToDestination(address)).First().satoshis) + Address = t.address, + Balance = (t.destination == null) ? 0 : new Money(this.coinView.GetBalance(t.destination).First().satoshis), + }).ToList() }; } @@ -83,10 +86,12 @@ public VerboseAddressBalancesResult GetAddressIndexerState(string[] addresses) return new VerboseAddressBalancesResult(this.IndexerTip.Height) { - BalancesData = addresses.Select(address => new AddressIndexerData() + BalancesData = addresses + .Select(address => (address, destination: AddressToDestination(address))) + .Select(t => new AddressIndexerData() { - Address = address, - BalanceChanges = ToDiff(this.coinView.GetBalance(AddressToDestination(address)).Select(b => new AddressBalanceChange() + Address = t.address, + BalanceChanges = (t.destination == null) ? new List() : ToDiff(this.coinView.GetBalance(t.destination).Select(b => new AddressBalanceChange() { BalanceChangedHeight = (int)b.height, Deposited = b.satoshis >= 0, From da3a657acef7ad797cd58d3abb7674fcd1755d56 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 17 Jun 2022 00:42:37 +1000 Subject: [PATCH 26/42] Fix ToDiff --- .../AddressIndexing/AddressIndexerCV.cs | 6 +++--- .../CoinViews/Coindb/Coindb.cs | 21 ++++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs index 7eff99c20d..faa5e8b8d2 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs @@ -71,9 +71,9 @@ private IEnumerable ToDiff(List addr for (int i = addressBalanceChanges.Count - 1; i > 0; i--) { yield return new AddressBalanceChange() { - BalanceChangedHeight = addressBalanceChanges[i].BalanceChangedHeight, - Deposited = addressBalanceChanges[i].Satoshi < addressBalanceChanges[i - 1].Satoshi, - Satoshi = Math.Abs(addressBalanceChanges[i].Satoshi - addressBalanceChanges[i - 1].Satoshi) + BalanceChangedHeight = addressBalanceChanges[i - 1].BalanceChangedHeight, + Deposited = addressBalanceChanges[i - 1].Satoshi < addressBalanceChanges[i].Satoshi, + Satoshi = Math.Abs(addressBalanceChanges[i - 1].Satoshi - addressBalanceChanges[i].Satoshi) }; } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 845a8f10f2..b7ed2317f0 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -82,14 +82,14 @@ public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTime balance = (row == null) ? 0 : BitConverter.ToInt64(row); } - foreach ((byte[] key, byte[] value) in this.coinDb.GetAll(BalanceAdjustmentTable, ascending: false, + foreach ((uint height, long adjustment) in this.coinDb.GetAll(BalanceAdjustmentTable, ascending: false, lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), includeLastKey: false, firstKey: txDestination.ToBytes(), - includeFirstKey: false)) + includeFirstKey: false).Select(x => (height: BitConverter.ToUInt32(x.Item1.Reverse().ToArray()), adjustment: BitConverter.ToInt64(x.Item2)))) { - yield return (BitConverter.ToUInt32(key.Reverse().ToArray()), balance); - balance -= BitConverter.ToInt64(value); + yield return (height, balance); + balance -= adjustment; } yield return (0, balance); @@ -148,7 +148,7 @@ public void SaveChanges(IList unspentOutputs, Dictionary> balanceUpdates) + private void AdjustBalance(ReadWriteBatch batch, Dictionary> balanceUpdates) { foreach ((TxDestination txDestination, Dictionary balanceAdjustments) in balanceUpdates) { @@ -446,16 +446,17 @@ private void AdjustBalance(IDbBatch batch, Dictionary k)) { var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); - byte[] row = this.coinDb.Get(BalanceAdjustmentTable, key); - long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + balanceAdjustments[height]; + byte[] row = batch.Get(BalanceAdjustmentTable, key); + long adjustment = balanceAdjustments[height]; + long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + adjustment; batch.Put(BalanceAdjustmentTable, key, BitConverter.GetBytes(balance)); - totalAdjustment += balance; + totalAdjustment += adjustment; } { var key = txDestination.ToBytes(); - byte[] row = this.coinDb.Get(BalanceTable, key); + byte[] row = batch.Get(BalanceTable, key); long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + totalAdjustment; batch.Put(BalanceTable, key, BitConverter.GetBytes(balance)); } From 0d65346048c1fab561711e2568943351fccb41a6 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 17 Jun 2022 15:01:04 +1000 Subject: [PATCH 27/42] Restore endianness fix still required for tests --- .../CoinViews/Coindb/Coindb.cs | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index b7ed2317f0..f07033c287 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -380,10 +380,75 @@ private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool for batch.Put(BlockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); } + private void EndiannessFix() + { + // The fix is still required for fixing up older LevelDb database coming from test zip files. + if (!(this.coinDb is LevelDb levelDb)) + return; + + LevelDB.DB leveldb = (LevelDB.DB)levelDb.GetType().GetField("db", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance).GetValue(levelDb); + + // Check if key bytes are in the wrong endian order. + HashHeightPair current = this.GetTipHash(); + + if (current == null) + return; + + byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(current.Height)); + // Fix the table if required. + if (row != null) + { + // To be sure, check the next height too. + byte[] row2 = (current.Height > 1) ? this.coinDb.Get(RewindTable, BitConverter.GetBytes(current.Height - 1)) : new byte[] { }; + if (row2 != null) + { + this.logger.LogInformation("Fixing the coin db."); + + var rows = new Dictionary(); + + using (var iterator = leveldb.CreateIterator()) + { + iterator.Seek(new byte[] { RewindTable }); + + while (iterator.IsValid()) + { + byte[] key = iterator.Key(); + + if (key.Length != 5 || key[0] != RewindTable) + break; + + int height = BitConverter.ToInt32(key, 1); + + rows[height] = iterator.Value(); + + iterator.Next(); + } + } + + using (var batch = new LevelDB.WriteBatch()) + { + foreach (int height in rows.Keys.OrderBy(k => k)) + { + batch.Delete(new byte[] { RewindTable }.Concat(BitConverter.GetBytes(height)).ToArray()); + } + + foreach (int height in rows.Keys.OrderBy(k => k)) + { + batch.Put(new byte[] { RewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray(), rows[height]); + } + + leveldb.Write(batch, new LevelDB.WriteOptions() { Sync = true }); + } + } + } + } + private void EnsureCoinDatabaseIntegrity() { this.logger.LogInformation("Checking coin database integrity..."); + this.EndiannessFix(); + if (this.GetTipHash() == null) { this.logger.LogInformation($"Rebuilding coin database that has no tip information."); From 06c90b388bdfa15e4f351c476b58569571a2a596 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sat, 2 Jul 2022 22:49:27 +1000 Subject: [PATCH 28/42] Fix merge --- src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs | 2 ++ src/Stratis.Bitcoin/Base/BaseFeature.cs | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs index 284dadd0ee..7208e35e64 100644 --- a/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs +++ b/src/Stratis.Bitcoin.Features.LightWallet/LightWalletFeature.cs @@ -14,6 +14,7 @@ using Stratis.Bitcoin.Configuration.Logging; using Stratis.Bitcoin.Configuration.Settings; using Stratis.Bitcoin.Connection; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Features.BlockStore; using Stratis.Bitcoin.Features.BlockStore.Pruning; using Stratis.Bitcoin.Features.LightWallet.Broadcasting; @@ -229,6 +230,7 @@ public static IFullNodeBuilder UseLightWallet(this IFullNodeBuilder fullNodeBuil services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/src/Stratis.Bitcoin/Base/BaseFeature.cs b/src/Stratis.Bitcoin/Base/BaseFeature.cs index 68d26bcfa7..31f50d4ab6 100644 --- a/src/Stratis.Bitcoin/Base/BaseFeature.cs +++ b/src/Stratis.Bitcoin/Base/BaseFeature.cs @@ -487,7 +487,6 @@ public static IFullNodeBuilder UseBaseFeature(this IFullNodeBuilder fullNodeBuil services.AddSingleton(); // Consensus - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); From 9800ba04a74c7b74264f66d08703293014aecf02 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sat, 2 Jul 2022 22:55:35 +1000 Subject: [PATCH 29/42] Fix merge --- .../AddressIndexing/AddressIndexerCV.cs | 1 + .../CoinViews/Coindb/Coindb.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs index faa5e8b8d2..f6e39cc88a 100644 --- a/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs +++ b/src/Stratis.Bitcoin.Features.BlockStore/AddressIndexing/AddressIndexerCV.cs @@ -3,6 +3,7 @@ using System.Linq; using NBitcoin; using Stratis.Bitcoin.Builder.Feature; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Controllers.Models; using Stratis.Bitcoin.Features.BlockStore.Models; using Stratis.Bitcoin.Features.Consensus.CoinViews; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index f07033c287..9b64dab1bd 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -6,6 +6,7 @@ using NBitcoin; using Stratis.Bitcoin.Configuration; using Stratis.Bitcoin.Configuration.Logging; +using Stratis.Bitcoin.Consensus; using Stratis.Bitcoin.Interfaces; using Stratis.Bitcoin.Utilities; From 4443a5e3f20724160a9f92d1f1a4f1132db0bf43 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 3 Jul 2022 20:31:47 +1000 Subject: [PATCH 30/42] Guarantee CoindDb's IScriptAddressReader dependncy --- .../FullNodeBuilderConsensusExtension.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs b/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs index 17b073e75e..ccb648f409 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/FullNodeBuilderConsensusExtension.cs @@ -80,6 +80,8 @@ public static IFullNodeBuilder UsePosConsensus(this IFullNodeBuilder fullNodeBui public static void ConfigureCoinDatabaseImplementation(this IServiceCollection services, DbType coindbType) { + services.Replace((p, old) => old ?? new ScriptAddressReader(), ServiceLifetime.Singleton); + switch (coindbType) { case DbType.Dbreeze: From d755442f9d59428715734ae6f9be585724f39da4 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 4 Jul 2022 23:44:15 +1000 Subject: [PATCH 31/42] Reduce changes --- .../CoinViews/CoinviewHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs index 73ad11b883..c8b041204e 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/CoinviewHelper.cs @@ -21,9 +21,9 @@ public OutPoint[] GetIdsToFetch(Block block, bool enforceBIP30) if (enforceBIP30) { // Calculate the hash outside the loop. - var txId = tx.GetHash(); + var txId = tx.GetHash(); foreach (var utxo in tx.Outputs.AsIndexedOutputs()) - ids.Add(new OutPoint() { Hash = txId, N = utxo.N }); + ids.Add(new OutPoint() { Hash = txId, N = utxo.N }); } if (!tx.IsCoinBase) From f83cc426d7a84fb570a507d55423b07ae9163085 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Mon, 4 Jul 2022 23:51:01 +1000 Subject: [PATCH 32/42] Reduce changes --- src/Stratis.CirrusDnsD/Properties/launchSettings.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 src/Stratis.CirrusDnsD/Properties/launchSettings.json diff --git a/src/Stratis.CirrusDnsD/Properties/launchSettings.json b/src/Stratis.CirrusDnsD/Properties/launchSettings.json deleted file mode 100644 index efe37df5eb..0000000000 --- a/src/Stratis.CirrusDnsD/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Stratis.CirrusDnsD": { - "commandName": "Project", - "commandLineArgs": "-dnsfullnode -dnsnameserver=cirrusmainseeder1.stratisnetwork.com -dnshostname=cirrusmain1.stratisnetwork.com -dnsmailbox=info@stratisplatform.com" - } - } -} \ No newline at end of file From 88bf88718e153b88534f12110ff90fb7a81ee904 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 5 Jul 2022 00:09:54 +1000 Subject: [PATCH 33/42] Remove unused code --- .../CoinViews/Coindb/Coindb.cs | 11 ----------- .../CoinViews/Coindb/ICoindb.cs | 8 -------- 2 files changed, 19 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 9b64dab1bd..0f21af95ea 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -209,17 +209,6 @@ public void SaveChanges(IList unspentOutputs, Dictionary - public int GetMinRewindHeight() - { - // Find the first row with a rewind table key prefix. - var res = this.coinDb.GetAll(RewindTable, keysOnly: true, firstKey: new byte[] { }).FirstOrDefault(); - if (res == default || res.Item1.Length != 5) - return -1; - - return BitConverter.ToInt32(res.Item1.SafeSubarray(0, 4).Reverse().ToArray()); - } - /// public HashHeightPair Rewind(HashHeightPair target) { diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs index f16f14723f..705d4679bc 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/ICoindb.cs @@ -72,14 +72,6 @@ public interface ICoindb /// See . RewindData GetRewindData(int height); - /// Gets the minimum rewind height. - /// - /// - /// The minimum rewind height or -1 if rewind is not possible. - /// - /// - int GetMinRewindHeight(); - /// /// Returns a combination of (height, satoshis) values with the cumulative balance up to the corresponding height. /// From 70c0512a4b45b7aaebc66f1612a77d79507a948e Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 7 Jul 2022 01:08:07 +1000 Subject: [PATCH 34/42] Refactor --- .../CoinViews/Coindb/Coindb.cs | 17 +- .../CoinViews/Coindb/IDb.cs | 105 +++++++++- .../CoinViews/Coindb/LevelDb.cs | 179 ++++++------------ .../CoinViews/Coindb/RocksDb.cs | 177 ++++++----------- 4 files changed, 237 insertions(+), 241 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 0f21af95ea..91dbbf19e9 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -83,14 +83,17 @@ public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTime balance = (row == null) ? 0 : BitConverter.ToInt64(row); } - foreach ((uint height, long adjustment) in this.coinDb.GetAll(BalanceAdjustmentTable, ascending: false, - lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), - includeLastKey: false, - firstKey: txDestination.ToBytes(), - includeFirstKey: false).Select(x => (height: BitConverter.ToUInt32(x.Item1.Reverse().ToArray()), adjustment: BitConverter.ToInt64(x.Item2)))) + using (var iterator = this.coinDb.GetIterator(BalanceAdjustmentTable)) { - yield return (height, balance); - balance -= adjustment; + foreach ((uint height, long adjustment) in iterator.GetAll(ascending: false, + lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), + includeLastKey: false, + firstKey: txDestination.ToBytes(), + includeFirstKey: false).Select(x => (height: BitConverter.ToUInt32(x.Item1.Reverse().ToArray()), adjustment: BitConverter.ToInt64(x.Item2)))) + { + yield return (height, balance); + balance -= adjustment; + } } yield return (0, balance); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index cfd3de967c..b624fadc40 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -11,8 +11,7 @@ public interface IDb : IDisposable byte[] Get(byte table, byte[] key); - IEnumerable<(byte[], byte[])> GetAll(byte table, bool keysOnly = false, bool ascending = true, - byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true); + IDbIterator GetIterator(byte table); IDbBatch GetWriteBatch(); @@ -79,4 +78,106 @@ public static ReadWriteBatch GetReadWriteBatch(this IDb db) return new ReadWriteBatch(db); } } + + public interface IDbIterator : IDisposable + { + void Seek(byte[] key); + void SeekToLast(); + void Next(); + void Prev(); + bool IsValid(); + byte[] Key(); + byte[] Value(); + } + + public static class IDbIteratorExt + { + private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + + public static IEnumerable<(byte[], byte[])> GetAll(this IDbIterator iterator, bool keysOnly = false, bool ascending = true, + byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) + { + bool done = false; + Func breakLoop; + Action next; + + if (!ascending) + { + // Seek to the last key if it was provided. + if (lastKey == null) + iterator.SeekToLast(); + else + { + iterator.Seek(lastKey); + if (!(includeLastKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), lastKey))) + iterator.Prev(); + } + + breakLoop = (firstKey == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, firstKey); + if (compareResult <= 0) + { + // If this is the first key and its not included or we've overshot the range then stop without yielding a value. + if (!includeFirstKey || compareResult < 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Prev(); + } + else /* Ascending */ + { + // Seek to the first key if it was provided. + if (firstKey == null) + iterator.Seek(new byte[0]); + else + { + iterator.Seek(firstKey); + if (!(includeFirstKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), firstKey))) + iterator.Next(); + } + + breakLoop = (lastKey == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, lastKey); + if (compareResult >= 0) + { + // If this is the last key and its not included or we've overshot the range then stop without yielding a value. + if (!includeLastKey || compareResult > 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Next(); + } + + while (iterator.IsValid()) + { + byte[] keyBytes = iterator.Key(); + + if (breakLoop != null && breakLoop(keyBytes)) + break; + + yield return (keyBytes, keysOnly ? null : iterator.Value()); + + if (done) + break; + + next(); + } + } + } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index a939e4174d..3359eb7058 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using LevelDB; using NBitcoin; @@ -31,10 +29,64 @@ public void Write() } } - public class LevelDb : IDb + public class LevelDbIterator : IDbIterator { - private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + byte table; + Iterator iterator; + + public LevelDbIterator(byte table, Iterator iterator) + { + this.table = table; + this.iterator = iterator; + } + + public void Seek(byte[] key) + { + this.iterator.Seek(new[] { this.table }.Concat(key).ToArray()); + } + + public void SeekToLast() + { + this.iterator.Seek(new[] { (byte)(this.table + 1) }); + if (!this.iterator.IsValid()) + this.iterator.SeekToLast(); + else + this.iterator.Prev(); + } + + public void Next() + { + this.iterator.Next(); + } + + public void Prev() + { + this.iterator.Prev(); + } + + public bool IsValid() + { + return this.iterator.IsValid() && this.iterator.Value()[0] == this.table; + } + public byte[] Key() + { + return this.iterator.Key().Skip(1).ToArray(); + } + + public byte[] Value() + { + return this.iterator.Value(); + } + + public void Dispose() + { + this.iterator.Dispose(); + } + } + + public class LevelDb : IDb + { private string name; DB db; @@ -43,6 +95,11 @@ public LevelDb() { } + public IDbIterator GetIterator(byte table) + { + return new LevelDbIterator(table, this.db.CreateIterator()); + } + public void Open(string name) { this.name = name; @@ -63,118 +120,6 @@ public byte[] Get(byte table, byte[] key) return this.db.Get(new[] { table }.Concat(key).ToArray()); } - public IEnumerable<(byte[], byte[])> GetAll(byte keyPrefix, bool keysOnly = false, bool ascending = true, - byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) - { - using (Iterator iterator = this.db.CreateIterator()) - { - byte[] firstKeyBytes = (firstKey == null) ? null : new[] { keyPrefix }.Concat(firstKey).ToArray(); - byte[] lastKeyBytes = (lastKey == null) ? null : new[] { keyPrefix }.Concat(lastKey).ToArray(); - bool done = false; - Func breakLoop; - Action next; - - if (!ascending) - { - if (lastKeyBytes == null) - { - // If no last key was provided then seek to the last record with this prefix - // by first seeking to the first record with the next prefix... - iterator.Seek(new[] { (byte)(keyPrefix + 1) }); - - // ...then back up to the previous value if the iterator is still valid. - if (iterator.IsValid()) - iterator.Prev(); - else - // If the iterator is invalid then there were no records with greater prefixes. - // In this case we can simply seek to the last record. - iterator.SeekToLast(); - } - else - { - // Seek to the last key if it was provided. - iterator.Seek(lastKeyBytes); - - // If it won't be returned, and is current/found, then move to the previous value. - if (!iterator.IsValid()) - iterator.SeekToLast(); - else if (!(includeLastKey && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes))) - iterator.Prev(); - } - - breakLoop = (firstKeyBytes == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, firstKeyBytes); - if (compareResult <= 0) - { - // If this is the first key and its not included or we've overshot the range then stop without yielding a value. - if (!includeFirstKey || compareResult < 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Prev(); - } - else /* Ascending */ - { - if (firstKeyBytes == null) - { - // If no first key was provided then use the key prefix to find the first value. - iterator.Seek(new[] { keyPrefix }); - } - else - { - // Seek to the first key if it was provided. - iterator.Seek(firstKeyBytes); - - // If it won't be returned, and is current/found, then move to the next value. - if (!includeFirstKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), firstKeyBytes)) - iterator.Next(); - } - - breakLoop = (lastKeyBytes == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, lastKeyBytes); - if (compareResult >= 0) - { - // If this is the last key and its not included or we've overshot the range then stop without yielding a value. - if (!includeLastKey || compareResult > 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Next(); - } - - while (iterator.IsValid()) - { - byte[] keyBytes = iterator.Key(); - - if (keyBytes[0] != keyPrefix || (breakLoop != null && breakLoop(keyBytes))) - break; - - yield return (keyBytes.Skip(1).ToArray(), keysOnly ? null : iterator.Value()); - - if (done) - break; - - next(); - } - } - } - public void Dispose() { this.db.Dispose(); diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs index 207b7e05a3..885608dadb 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using RocksDbSharp; using NBitcoin; @@ -31,6 +29,62 @@ public void Write() } } + public class RocksDbIterator : IDbIterator + { + byte table; + Iterator iterator; + + public RocksDbIterator(byte table, Iterator iterator) + { + this.table = table; + this.iterator = iterator; + } + + public void Seek(byte[] key) + { + this.iterator.Seek(new[] { this.table }.Concat(key).ToArray()); + } + + public void SeekToLast() + { + this.iterator.Seek(new[] { (byte)(this.table + 1) }); + if (!this.iterator.Valid()) + this.iterator.SeekToLast(); + else + this.iterator.Prev(); + } + + public void Next() + { + this.iterator.Next(); + } + + public void Prev() + { + this.iterator.Prev(); + } + + public bool IsValid() + { + return this.iterator.Valid() && this.iterator.Value()[0] == this.table; + } + + public byte[] Key() + { + return this.iterator.Key().Skip(1).ToArray(); + } + + public byte[] Value() + { + return this.iterator.Value(); + } + + public void Dispose() + { + this.iterator.Dispose(); + } + } + public class RocksDb : IDb { private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); @@ -43,6 +97,11 @@ public RocksDb() { } + public IDbIterator GetIterator(byte table) + { + return new RocksDbIterator(table, this.db.NewIterator()); + } + public void Open(string name) { this.name = name; @@ -63,118 +122,6 @@ public byte[] Get(byte table, byte[] key) return this.db.Get(new[] { table }.Concat(key).ToArray()); } - public IEnumerable<(byte[], byte[])> GetAll(byte keyPrefix, bool keysOnly = false, bool ascending = true, - byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) - { - using (Iterator iterator = this.db.NewIterator()) - { - byte[] firstKeyBytes = (firstKey == null) ? null : new[] { keyPrefix }.Concat(firstKey).ToArray(); - byte[] lastKeyBytes = (lastKey == null) ? null : new[] { keyPrefix }.Concat(lastKey).ToArray(); - bool done = false; - Func breakLoop; - Action next; - - if (!ascending) - { - if (lastKeyBytes == null) - { - // If no last key was provided then seek to the last record with this prefix - // by first seeking to the first record with the next prefix... - iterator.Seek(new[] { (byte)(keyPrefix + 1) }); - - // ...then back up to the previous value if the iterator is still valid. - if (iterator.Valid()) - iterator.Prev(); - else - // If the iterator is invalid then there were no records with greater prefixes. - // In this case we can simply seek to the last record. - iterator.SeekToLast(); - } - else - { - // Seek to the last key if it was provided. - iterator.Seek(lastKeyBytes); - - // If it won't be returned, and is current/found, then move to the previous value. - if (!iterator.Valid()) - iterator.SeekToLast(); - else if (!(includeLastKey && byteArrayComparer.Equals(iterator.Key(), lastKeyBytes))) - iterator.Prev(); - } - - breakLoop = (firstKeyBytes == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, firstKeyBytes); - if (compareResult <= 0) - { - // If this is the first key and its not included or we've overshot the range then stop without yielding a value. - if (!includeFirstKey || compareResult < 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Prev(); - } - else /* Ascending */ - { - if (firstKeyBytes == null) - { - // If no first key was provided then use the key prefix to find the first value. - iterator.Seek(new[] { keyPrefix }); - } - else - { - // Seek to the first key if it was provided. - iterator.Seek(firstKeyBytes); - - // If it won't be returned, and is current/found, then move to the next value. - if (!includeFirstKey && iterator.Valid() && byteArrayComparer.Equals(iterator.Key(), firstKeyBytes)) - iterator.Next(); - } - - breakLoop = (lastKeyBytes == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, lastKeyBytes); - if (compareResult >= 0) - { - // If this is the last key and its not included or we've overshot the range then stop without yielding a value. - if (!includeLastKey || compareResult > 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Next(); - } - - while (iterator.Valid()) - { - byte[] keyBytes = iterator.Key(); - - if (keyBytes[0] != keyPrefix || (breakLoop != null && breakLoop(keyBytes))) - break; - - yield return (keyBytes.Skip(1).ToArray(), keysOnly ? null : iterator.Value()); - - if (done) - break; - - next(); - } - } - } - public void Dispose() { this.db.Dispose(); From b93c277b4dbd8cefb5432a53f6ea27fb839ed3a9 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 7 Jul 2022 17:07:33 +1000 Subject: [PATCH 35/42] Add XML comments --- .../CoinViews/Coindb/Coindb.cs | 4 +- .../CoinViews/Coindb/IDb.cs | 138 +++++++++++++++++- .../CoinViews/Coindb/LevelDb.cs | 81 +++++----- .../CoinViews/Coindb/RocksDb.cs | 83 +++++------ 4 files changed, 218 insertions(+), 88 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index 91dbbf19e9..aaf7cf4d1d 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -110,12 +110,12 @@ public void Initialize(bool balanceIndexingEnabled) EnsureCoinDatabaseIntegrity(); - Block genesis = this.network.GetGenesis(); - if (this.GetTipHash() == null) { using (var batch = this.coinDb.GetWriteBatch()) { + Block genesis = this.network.GetGenesis(); + this.SetBlockHash(batch, new HashHeightPair(genesis.GetHash(), 0)); batch.Write(); } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index b624fadc40..bed1a1ffe8 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -5,28 +5,80 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { + /// + /// This interface and its relevant implementations provides a common way to interact with and databases. + /// public interface IDb : IDisposable { - void Open(string name); - + /// + /// Opens the database at the specified path. + /// + /// The path where the database is located. + void Open(string dbPath); + + /// + /// Gets the value associated with a table and key. + /// + /// The table identifier. + /// The key of the value to retrieve. + /// The value for the specified table and key. byte[] Get(byte table, byte[] key); + /// + /// Gets an iterator that allows iteration over keys in a table. + /// + /// The table that will be iterated. + /// See . IDbIterator GetIterator(byte table); + /// + /// Gets a batch that can be used to record changes that can be applied atomically. + /// + /// The method will not reflect these changes until they are committed. + /// See . IDbBatch GetWriteBatch(); + /// + /// Removes all tables and their contents. + /// void Clear(); } + /// + /// A batch that can be used to record changes that can be applied atomically. + /// + /// The database's method will not reflect these changes until they are committed. public interface IDbBatch : IDisposable { + /// + /// Records a value that will be written to the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that identifies the value to be updated. + /// The value to be written to the table. + /// This class for fluent operations. IDbBatch Put(byte table, byte[] key, byte[] value); + /// + /// Records a key that will be deleted from the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that will be removed. + /// This class for fluent operations. IDbBatch Delete(byte table, byte[] key); + /// + /// Writes the recorded changes to the database. + /// void Write(); } + /// + /// A batch that can be used to record changes that can be applied atomically. + /// + /// The supplied method will immediately reflect any changes that have + /// been made or retrieve the value from the underlying database. However the database method + /// will only show the changes after the method is called. public class ReadWriteBatch : IDbBatch { private readonly IDb db; @@ -40,18 +92,37 @@ public ReadWriteBatch(IDb db) this.cache = new Dictionary(new ByteArrayComparer()); } + /// + /// Records a value that will be written to the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that identifies the value to be updated. + /// The value to be written to the table. + /// This class for fluent operations. public IDbBatch Put(byte table, byte[] key, byte[] value) { this.cache[new byte[] { table }.Concat(key).ToArray()] = value; return this.batch.Put(table, key, value); } + /// + /// Records a key that will be deleted from the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that will be removed. + /// This interface for fluent operations. public IDbBatch Delete(byte table, byte[] key) { this.cache[new byte[] { table }.Concat(key).ToArray()] = null; return this.batch.Delete(table, key); } + /// + /// Returns any changes that have been made to the batch or retrieves the value from the underlying database.. + /// + /// The table of the value to be retrieved. + /// The table key of the value to retrieve. + /// This interface for fluent operations. public byte[] Get(byte table, byte[] key) { if (this.cache.TryGetValue(new byte[] { table }.Concat(key).ToArray(), out byte[] value)) @@ -60,6 +131,9 @@ public byte[] Get(byte table, byte[] key) return this.db.Get(table, key); } + /// + /// Writes the recorded changes to the database. + /// public void Write() { this.batch.Write(); @@ -71,29 +145,89 @@ public void Dispose() } } + /// + /// Extension methods that build on the interface. + /// public static class IDbExt { + /// + /// Gets a . + /// + /// The database to get the batch for. + /// The . public static ReadWriteBatch GetReadWriteBatch(this IDb db) { return new ReadWriteBatch(db); } } + /// + /// An iterator that can be used to iterate the keys and values in an compliant database. + /// public interface IDbIterator : IDisposable { + /// + /// Seeks to a first key >= in the relevant table. + /// If no such key is found then will return false. + /// + /// The key to find. void Seek(byte[] key); + + /// + /// Seeks to the last key in the relevant table. + /// If no such key is found then will return false. + /// void SeekToLast(); + + /// + /// Seeks to the next key in the relevant table. + /// If no such key is found then will return false. + /// void Next(); + + /// + /// Seeks to the previous key in the relevant table. + /// If no such key is found then will return false. + /// void Prev(); + + /// + /// Determines if the current key is valid. + /// + /// true if a , , or operation found a valid key. false otherwise. bool IsValid(); + + /// + /// The current key. + /// + /// The key. byte[] Key(); + + /// + /// The current value. + /// + /// The value. byte[] Value(); } + /// + /// Extension methods that build on the interface. + /// public static class IDbIteratorExt { private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + /// + /// Gets all the keys in the relevant table subject to any supplied constraints. + /// + /// The iterator that also identifies the table being iterated. + /// Defaults to false. Set to true if values should be ommitted - i.e. set to null. + /// Defaults to true. Set to false to return keys in ascending order. + /// Can be set optionally to specify the lower bound of keys to return. + /// Can be set optionally to specify the upper bound of keys to return. + /// Defaults to true. Set to false to omit the key specified in . + /// Defaults to true. Set to false to omit the key specified in . + /// An enumeration containing all the keys and values according to the specified constraints. public static IEnumerable<(byte[], byte[])> GetAll(this IDbIterator iterator, bool keysOnly = false, bool ascending = true, byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) { diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index 3359eb7058..ccb5203dc2 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -4,6 +4,45 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { + /// + public class LevelDb : IDb + { + private string dbPath; + + DB db; + + public IDbIterator GetIterator(byte table) + { + return new LevelDbIterator(table, this.db.CreateIterator()); + } + + public void Open(string dbPath) + { + this.dbPath = dbPath; + this.db = new DB(new Options() { CreateIfMissing = true }, dbPath); + } + + public void Clear() + { + this.db.Dispose(); + System.IO.Directory.Delete(this.dbPath, true); + this.db = new DB(new Options() { CreateIfMissing = true }, this.dbPath); + } + + public IDbBatch GetWriteBatch() => new LevelDbBatch(this.db); + + public byte[] Get(byte table, byte[] key) + { + return this.db.Get(new[] { table }.Concat(key).ToArray()); + } + + public void Dispose() + { + this.db.Dispose(); + } + } + + /// public class LevelDbBatch : WriteBatch, IDbBatch { DB db; @@ -29,6 +68,7 @@ public void Write() } } + /// public class LevelDbIterator : IDbIterator { byte table; @@ -84,45 +124,4 @@ public void Dispose() this.iterator.Dispose(); } } - - public class LevelDb : IDb - { - private string name; - - DB db; - - public LevelDb() - { - } - - public IDbIterator GetIterator(byte table) - { - return new LevelDbIterator(table, this.db.CreateIterator()); - } - - public void Open(string name) - { - this.name = name; - this.db = new DB(new Options() { CreateIfMissing = true }, name); - } - - public void Clear() - { - this.db.Dispose(); - System.IO.Directory.Delete(this.name, true); - this.db = new DB(new Options() { CreateIfMissing = true }, this.name); - } - - public IDbBatch GetWriteBatch() => new LevelDbBatch(this.db); - - public byte[] Get(byte table, byte[] key) - { - return this.db.Get(new[] { table }.Concat(key).ToArray()); - } - - public void Dispose() - { - this.db.Dispose(); - } - } } diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs index 885608dadb..3b5063f701 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs @@ -4,6 +4,45 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { + /// + public class RocksDb : IDb + { + private string dbPath; + + RocksDbSharp.RocksDb db; + + public IDbIterator GetIterator(byte table) + { + return new RocksDbIterator(table, this.db.NewIterator()); + } + + public void Open(string dbPath) + { + this.dbPath = dbPath; + this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), dbPath); + } + + public void Clear() + { + this.db.Dispose(); + System.IO.Directory.Delete(this.dbPath, true); + this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), this.dbPath); + } + + public IDbBatch GetWriteBatch() => new RocksDbBatch(this.db); + + public byte[] Get(byte table, byte[] key) + { + return this.db.Get(new[] { table }.Concat(key).ToArray()); + } + + public void Dispose() + { + this.db.Dispose(); + } + } + + /// public class RocksDbBatch : WriteBatch, IDbBatch { RocksDbSharp.RocksDb db; @@ -29,6 +68,7 @@ public void Write() } } + /// public class RocksDbIterator : IDbIterator { byte table; @@ -84,47 +124,4 @@ public void Dispose() this.iterator.Dispose(); } } - - public class RocksDb : IDb - { - private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); - - private string name; - - RocksDbSharp.RocksDb db; - - public RocksDb() - { - } - - public IDbIterator GetIterator(byte table) - { - return new RocksDbIterator(table, this.db.NewIterator()); - } - - public void Open(string name) - { - this.name = name; - this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), name); - } - - public void Clear() - { - this.db.Dispose(); - System.IO.Directory.Delete(this.name, true); - this.db = RocksDbSharp.RocksDb.Open(new DbOptions().SetCreateIfMissing(), this.name); - } - - public IDbBatch GetWriteBatch() => new RocksDbBatch(this.db); - - public byte[] Get(byte table, byte[] key) - { - return this.db.Get(new[] { table }.Concat(key).ToArray()); - } - - public void Dispose() - { - this.db.Dispose(); - } - } } From c92a9ae4db73fcf47b29bf886c7b0b1b575f2829 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 7 Jul 2022 17:16:15 +1000 Subject: [PATCH 36/42] Add XML comments --- .../CoinViews/Coindb/LevelDb.cs | 6 +++--- .../CoinViews/Coindb/RocksDb.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs index ccb5203dc2..20696f5cb0 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/LevelDb.cs @@ -4,7 +4,7 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { - /// + /// A minimal LevelDb wrapper that makes it compliant with the interface. public class LevelDb : IDb { private string dbPath; @@ -42,7 +42,7 @@ public void Dispose() } } - /// + /// A minimal LevelDb wrapper that makes it compliant with the interface. public class LevelDbBatch : WriteBatch, IDbBatch { DB db; @@ -68,7 +68,7 @@ public void Write() } } - /// + /// A minimal LevelDb wrapper that makes it compliant with the interface. public class LevelDbIterator : IDbIterator { byte table; diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs index 3b5063f701..ddf88030af 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/RocksDb.cs @@ -4,7 +4,7 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews { - /// + /// A minimal RocksDb wrapper that makes it compliant with the interface. public class RocksDb : IDb { private string dbPath; @@ -42,7 +42,7 @@ public void Dispose() } } - /// + /// A minimal RocksDb wrapper that makes it compliant with the interface. public class RocksDbBatch : WriteBatch, IDbBatch { RocksDbSharp.RocksDb db; @@ -68,7 +68,7 @@ public void Write() } } - /// + /// A minimal RocksDb wrapper that makes it compliant with the interface. public class RocksDbIterator : IDbIterator { byte table; From b1c1482e0d2378eb12f935953096d1f60a25368c Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 7 Jul 2022 17:19:42 +1000 Subject: [PATCH 37/42] Modify XML comment --- src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs index bed1a1ffe8..09538fcbe3 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/IDb.cs @@ -77,7 +77,7 @@ public interface IDbBatch : IDisposable /// A batch that can be used to record changes that can be applied atomically. /// /// The supplied method will immediately reflect any changes that have - /// been made or retrieve the value from the underlying database. However the database method + /// been made or retrieve the value from the underlying database. In contrast the database method /// will only show the changes after the method is called. public class ReadWriteBatch : IDbBatch { From 0083e09322e277f31bbcc11d0d076cf451810c83 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 12 Jul 2022 17:59:53 +1000 Subject: [PATCH 38/42] Fix merge --- src/NBitcoin/PubKey.cs | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/NBitcoin/PubKey.cs b/src/NBitcoin/PubKey.cs index 06dbb83011..d1c9711205 100644 --- a/src/NBitcoin/PubKey.cs +++ b/src/NBitcoin/PubKey.cs @@ -174,9 +174,13 @@ public bool Verify(uint256 hash, byte[] sig) return Verify(hash, ECDSASignature.FromDER(sig)); } + private string hexStr = null; + public string ToHex() { - return Encoders.Hex.EncodeData(this.vch); + this.hexStr ??= Encoders.Hex.EncodeData(this.vch); + + return this.hexStr; } #region IBitcoinSerializable Members @@ -184,7 +188,12 @@ public string ToHex() public void ReadWrite(BitcoinStream stream) { stream.ReadWrite(ref this.vch); - if(!stream.Serializing) this._ECKey = new ECKey(this.vch, false); + + if (!stream.Serializing) + { + this._ECKey = new ECKey(this.vch, false); + this.hexStr = null; + } } #endregion @@ -345,18 +354,33 @@ public PubKey Derivate(byte[] cc, uint nChild, out byte[] ccChild) public override bool Equals(object obj) { - var item = obj as PubKey; - if(item == null) + return obj != null && Equals(obj as PubKey); + } + + public bool Equals(PubKey other) + { + if ((object)other == null) + return false; + + if (other.vch.Length != this.vch.Length) return false; - return ToHex().Equals(item.ToHex()); + + for (int i = 0; i < other.vch.Length; i++) + if (other.vch[i] != this.vch[i]) + return false; + + return true; } + public static bool operator ==(PubKey a, PubKey b) { - if(ReferenceEquals(a, b)) + if (ReferenceEquals(a, b)) return true; - if(((object)a == null) || ((object)b == null)) + + if (((object)a == null) != ((object)b == null)) return false; - return a.ToHex() == b.ToHex(); + + return a.Equals(b); } public static bool operator !=(PubKey a, PubKey b) @@ -366,17 +390,19 @@ public override bool Equals(object obj) public override int GetHashCode() { - return ToHex().GetHashCode(); + return (((((this.vch[1] << 8) + this.vch[2]) << 8) + this.vch[3]) << 8) + this.vch[4]; } public PubKey UncoverSender(Key ephem, PubKey scan) { return Uncover(ephem, scan); } + public PubKey UncoverReceiver(Key scan, PubKey ephem) { return Uncover(scan, ephem); } + public PubKey Uncover(Key priv, PubKey pub) { X9ECParameters curve = ECKey.Secp256k1; @@ -473,4 +499,4 @@ public BitcoinWitPubKeyAddress GetSegwitAddress(Network network) #endregion } -} +} \ No newline at end of file From 98a3ef50d4b8c9cc75629808843b02d3e3178d5e Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 12 Jul 2022 18:00:53 +1000 Subject: [PATCH 39/42] Fix merge --- src/NBitcoin/PubKey.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NBitcoin/PubKey.cs b/src/NBitcoin/PubKey.cs index d1c9711205..f95a0edcfb 100644 --- a/src/NBitcoin/PubKey.cs +++ b/src/NBitcoin/PubKey.cs @@ -499,4 +499,4 @@ public BitcoinWitPubKeyAddress GetSegwitAddress(Network network) #endregion } -} \ No newline at end of file +} From 6fe610a9ed6c0e3e0c6ea6cb19055f59439a255c Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 12 Jul 2022 18:15:43 +1000 Subject: [PATCH 40/42] Reorganize code --- .../CoinViews/Coindb/Coindb.cs | 147 +++++++++--------- 1 file changed, 77 insertions(+), 70 deletions(-) diff --git a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs index d2079c36d6..2b48ddb927 100644 --- a/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs +++ b/src/Stratis.Bitcoin.Features.Consensus/CoinViews/Coindb/Coindb.cs @@ -19,52 +19,59 @@ namespace Stratis.Bitcoin.Features.Consensus.CoinViews /// A database supporting the interface. public class Coindb : ICoindb, IStakedb, IDisposable where T : IDb, new() { - public const byte CoinsTable = 1; - public const byte BlockTable = 2; - public const byte RewindTable = 3; - public const byte StakeTable = 4; - public const byte BalanceTable = 5; - public const byte BalanceAdjustmentTable = 6; - /// Database key under which the block hash of the coin view's current tip is stored. private static readonly byte[] blockHashKey = new byte[0]; + private static readonly byte coinsTable = 1; + private static readonly byte blockTable = 2; + private static readonly byte rewindTable = 3; + private static readonly byte stakeTable = 4; + private static readonly byte balanceTable = 5; + private static readonly byte balanceAdjustmentTable = 6; + /// Database key under which the block hash of the coin view's last indexed tip is stored. private static readonly byte[] blockIndexedHashKey = new byte[1]; + private readonly string dataFolder; + /// Instance logger. - private ILogger logger; + private readonly ILogger logger; - public bool BalanceIndexingEnabled { get; private set; } + /// Specification of the network the node runs on - regtest/testnet/mainnet. + private readonly Network network; - /// Access to dBreeze database. - private IDb coinDb; + public bool BalanceIndexingEnabled { get; private set; } /// Hash of the block which is currently the tip of the coinview. private HashHeightPair persistedCoinviewTip; private readonly IScriptAddressReader scriptAddressReader; - private readonly string dataFolder; - - /// Specification of the network the node runs on - regtest/testnet/mainnet. - private readonly Network network; - /// Performance counter to measure performance of the database insert and query operations. private readonly BackendPerformanceCounter performanceCounter; private BackendPerformanceSnapshot latestPerformanceSnapShot; + /// Access to database. + private IDb coinDb; + private readonly DBreezeSerializer dBreezeSerializer; private const int MaxRewindBatchSize = 10000; - public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) + public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTimeProvider, + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) + : this(network, dataFolder.CoindbPath, dateTimeProvider, nodeStats, dBreezeSerializer, scriptAddressReader) + { + } + + public Coindb(Network network, string dataFolder, IDateTimeProvider dateTimeProvider, + INodeStats nodeStats, DBreezeSerializer dBreezeSerializer, IScriptAddressReader scriptAddressReader) { Guard.NotNull(network, nameof(network)); - Guard.NotNull(dataFolder, nameof(dataFolder)); + Guard.NotEmpty(dataFolder, nameof(dataFolder)); - this.dataFolder = dataFolder.CoindbPath; + this.dataFolder = dataFolder; this.dBreezeSerializer = dBreezeSerializer; this.logger = LogManager.GetCurrentClassLogger(); this.network = network; @@ -75,31 +82,6 @@ public Coindb(Network network, DataFolder dataFolder, IDateTimeProvider dateTime nodeStats.RegisterStats(this.AddBenchStats, StatsType.Benchmark, this.GetType().Name, 400); } - /// - public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) - { - long balance; - { - byte[] row = this.coinDb.Get(BalanceTable, txDestination.ToBytes()); - balance = (row == null) ? 0 : BitConverter.ToInt64(row); - } - - using (var iterator = this.coinDb.GetIterator(BalanceAdjustmentTable)) - { - foreach ((uint height, long adjustment) in iterator.GetAll(ascending: false, - lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), - includeLastKey: false, - firstKey: txDestination.ToBytes(), - includeFirstKey: false).Select(x => (height: BitConverter.ToUInt32(x.Item1.Reverse().ToArray()), adjustment: BitConverter.ToInt64(x.Item2)))) - { - yield return (height, balance); - balance -= adjustment; - } - } - - yield return (0, balance); - } - /// public void Initialize(bool balanceIndexingEnabled) { @@ -136,7 +118,7 @@ public FetchCoinsResponse FetchCoins(OutPoint[] utxos) foreach (OutPoint outPoint in utxos) { - byte[] row = this.coinDb.Get(CoinsTable, outPoint.ToBytes()); + byte[] row = this.coinDb.Get(coinsTable, outPoint.ToBytes()); Coins outputs = row != null ? this.dBreezeSerializer.Deserialize(row) : null; this.logger.LogDebug("Outputs for '{0}' were {1}.", outPoint, outputs == null ? "NOT loaded" : "loaded"); @@ -174,7 +156,7 @@ public void SaveChanges(IList unspentOutputs, Dictionary unspentOutputs, Dictionary unspentOutputs, Dictionary public RewindData GetRewindData(int height) { - byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(height).Reverse().ToArray()); return row != null ? this.dBreezeSerializer.Deserialize(row) : null; } @@ -236,7 +218,7 @@ public void PutStake(IEnumerable stakeEntries) { if (!stakeEntry.InStore) { - batch.Put(StakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); + batch.Put(stakeTable, stakeEntry.BlockId.ToBytes(false), this.dBreezeSerializer.Serialize(stakeEntry.BlockStake)); stakeEntry.InStore = true; } } @@ -251,7 +233,7 @@ public void GetStake(IEnumerable blocklist) foreach (StakeItem blockStake in blocklist) { this.logger.LogTrace("Loading POS block hash '{0}' from the database.", blockStake.BlockId); - byte[] stakeRow = this.coinDb.Get(StakeTable, blockStake.BlockId.ToBytes(false)); + byte[] stakeRow = this.coinDb.Get(stakeTable, blockStake.BlockId.ToBytes(false)); if (stakeRow != null) { @@ -266,7 +248,7 @@ public HashHeightPair GetTipHash() { if (this.persistedCoinviewTip == null) { - var row = this.coinDb.Get(BlockTable, blockHashKey); + var row = this.coinDb.Get(blockTable, blockHashKey); if (row != null) { this.persistedCoinviewTip = new HashHeightPair(); @@ -279,7 +261,7 @@ public HashHeightPair GetTipHash() private HashHeightPair GetIndexedTipHash() { - var row = this.coinDb.Get(BlockTable, blockIndexedHashKey); + var row = this.coinDb.Get(blockTable, blockIndexedHashKey); if (row != null) { var tip = new HashHeightPair(); @@ -292,7 +274,7 @@ private HashHeightPair GetIndexedTipHash() private bool TryGetCoins(ReadWriteBatch readWriteBatch, byte[] key, out Coins coins) { - byte[] row2 = readWriteBatch.Get(CoinsTable, key); + byte[] row2 = readWriteBatch.Get(coinsTable, key); if (row2 == null) { coins = null; @@ -317,12 +299,12 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) for (int height = startHeight; height > (target?.Height ?? (startHeight - 1)) && height > (startHeight - MaxRewindBatchSize); height--) { byte[] rowKey = BitConverter.GetBytes(height).Reverse().ToArray(); - byte[] row = this.coinDb.Get(RewindTable, rowKey); + byte[] row = this.coinDb.Get(rewindTable, rowKey); if (row == null) throw new InvalidOperationException($"No rewind data found for block at height {height}."); - batch.Delete(RewindTable, rowKey); + batch.Delete(rewindTable, rowKey); var rewindData = this.dBreezeSerializer.Deserialize(row); @@ -336,7 +318,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) if (height <= indexedHeight) Update(balanceAdjustments, coins.TxOut.ScriptPubKey, coins.Height, -coins.TxOut.Value); - batch.Delete(CoinsTable, key); + batch.Delete(coinsTable, key); } else { @@ -347,7 +329,7 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) foreach (RewindDataOutput rewindDataOutput in rewindData.OutputsToRestore) { this.logger.LogDebug("Outputs of outpoint '{0}' will be restored.", rewindDataOutput.OutPoint); - batch.Put(CoinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); + batch.Put(coinsTable, rewindDataOutput.OutPoint.ToBytes(), this.dBreezeSerializer.Serialize(rewindDataOutput.Coins)); if (height <= indexedHeight) Update(balanceAdjustments, rewindDataOutput.Coins.TxOut.ScriptPubKey, (uint)height, rewindDataOutput.Coins.TxOut.Value); @@ -369,9 +351,9 @@ private HashHeightPair RewindInternal(int startHeight, HashHeightPair target) private void SetBlockHash(IDbBatch batch, HashHeightPair nextBlockHash, bool forceUpdateIndexedHeight = false) { this.persistedCoinviewTip = nextBlockHash; - batch.Put(BlockTable, blockHashKey, nextBlockHash.ToBytes()); + batch.Put(blockTable, blockHashKey, nextBlockHash.ToBytes()); if (this.BalanceIndexingEnabled || forceUpdateIndexedHeight) - batch.Put(BlockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); + batch.Put(blockTable, blockIndexedHashKey, nextBlockHash.ToBytes()); } private void EndiannessFix() @@ -388,12 +370,12 @@ private void EndiannessFix() if (current == null) return; - byte[] row = this.coinDb.Get(RewindTable, BitConverter.GetBytes(current.Height)); + byte[] row = this.coinDb.Get(rewindTable, BitConverter.GetBytes(current.Height)); // Fix the table if required. if (row != null) { // To be sure, check the next height too. - byte[] row2 = (current.Height > 1) ? this.coinDb.Get(RewindTable, BitConverter.GetBytes(current.Height - 1)) : new byte[] { }; + byte[] row2 = (current.Height > 1) ? this.coinDb.Get(rewindTable, BitConverter.GetBytes(current.Height - 1)) : new byte[] { }; if (row2 != null) { this.logger.LogInformation("Fixing the coin db."); @@ -402,13 +384,13 @@ private void EndiannessFix() using (var iterator = leveldb.CreateIterator()) { - iterator.Seek(new byte[] { RewindTable }); + iterator.Seek(new byte[] { rewindTable }); while (iterator.IsValid()) { byte[] key = iterator.Key(); - if (key.Length != 5 || key[0] != RewindTable) + if (key.Length != 5 || key[0] != rewindTable) break; int height = BitConverter.ToInt32(key, 1); @@ -423,12 +405,12 @@ private void EndiannessFix() { foreach (int height in rows.Keys.OrderBy(k => k)) { - batch.Delete(new byte[] { RewindTable }.Concat(BitConverter.GetBytes(height)).ToArray()); + batch.Delete(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height)).ToArray()); } foreach (int height in rows.Keys.OrderBy(k => k)) { - batch.Put(new byte[] { RewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray(), rows[height]); + batch.Put(new byte[] { rewindTable }.Concat(BitConverter.GetBytes(height).Reverse()).ToArray(), rows[height]); } leveldb.Write(batch, new LevelDB.WriteOptions() { Sync = true }); @@ -505,19 +487,19 @@ private void AdjustBalance(ReadWriteBatch batch, Dictionary k)) { var key = txDestination.ToBytes().Concat(BitConverter.GetBytes(height).Reverse()).ToArray(); - byte[] row = batch.Get(BalanceAdjustmentTable, key); + byte[] row = batch.Get(balanceAdjustmentTable, key); long adjustment = balanceAdjustments[height]; long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + adjustment; - batch.Put(BalanceAdjustmentTable, key, BitConverter.GetBytes(balance)); + batch.Put(balanceAdjustmentTable, key, BitConverter.GetBytes(balance)); totalAdjustment += adjustment; } { var key = txDestination.ToBytes(); - byte[] row = batch.Get(BalanceTable, key); + byte[] row = batch.Get(balanceTable, key); long balance = ((row == null) ? 0 : BitConverter.ToInt64(row)) + totalAdjustment; - batch.Put(BalanceTable, key, BitConverter.GetBytes(balance)); + batch.Put(balanceTable, key, BitConverter.GetBytes(balance)); } } } @@ -544,6 +526,31 @@ private void Update(Dictionary> balanceAdj } } + /// + public IEnumerable<(uint height, long satoshis)> GetBalance(TxDestination txDestination) + { + long balance; + { + byte[] row = this.coinDb.Get(balanceTable, txDestination.ToBytes()); + balance = (row == null) ? 0 : BitConverter.ToInt64(row); + } + + using (var iterator = this.coinDb.GetIterator(balanceAdjustmentTable)) + { + foreach ((uint height, long adjustment) in iterator.GetAll(ascending: false, + lastKey: txDestination.ToBytes().Concat(BitConverter.GetBytes(this.persistedCoinviewTip.Height + 1).Reverse()).ToArray(), + includeLastKey: false, + firstKey: txDestination.ToBytes(), + includeFirstKey: false).Select(x => (height: BitConverter.ToUInt32(x.Item1.Reverse().ToArray()), adjustment: BitConverter.ToInt64(x.Item2)))) + { + yield return (height, balance); + balance -= adjustment; + } + } + + yield return (0, balance); + } + /// public void Dispose() { From bff166b80bfd11b5c757afdab54cd33f89fe8042 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 12 Jul 2022 18:28:46 +1000 Subject: [PATCH 41/42] Split code into separate files --- src/Stratis.Bitcoin/Database/IDb.cs | 205 +----------------- .../Database/IDbIteratorExt.cs | 111 ++++++++++ .../Database/ReadWriteBatch.cs | 94 ++++++++ 3 files changed, 212 insertions(+), 198 deletions(-) create mode 100644 src/Stratis.Bitcoin/Database/IDbIteratorExt.cs create mode 100644 src/Stratis.Bitcoin/Database/ReadWriteBatch.cs diff --git a/src/Stratis.Bitcoin/Database/IDb.cs b/src/Stratis.Bitcoin/Database/IDb.cs index 443ab7add2..83f727b015 100644 --- a/src/Stratis.Bitcoin/Database/IDb.cs +++ b/src/Stratis.Bitcoin/Database/IDb.cs @@ -1,13 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using NBitcoin; namespace Stratis.Bitcoin.Database { /// - /// This interface and its relevant implementations provides a common way to interact with and databases. + /// This interface and its relevant implementations provide a standardized interface to databases such as and , or other databases + /// capable of supporting key-based value retrieval and key iteration. /// + /// + /// The interface expects keys to be specified as separate table and key identifiers. Similarly iterators are expected to be constrained to operate within single tables. + /// public interface IDb : IDisposable { /// @@ -34,7 +35,8 @@ public interface IDb : IDisposable /// /// Gets a batch that can be used to record changes that can be applied atomically. /// - /// The method will not reflect these changes until they are committed. + /// The method will not reflect these changes until they are committed. Use + /// the class if uncommitted changes need to be accessed. /// See . IDbBatch GetWriteBatch(); @@ -73,94 +75,6 @@ public interface IDbBatch : IDisposable void Write(); } - /// - /// A batch that can be used to record changes that can be applied atomically. - /// - /// The supplied method will immediately reflect any changes that have - /// been made or retrieve the value from the underlying database. In contrast the database method - /// will only show the changes after the method is called. - public class ReadWriteBatch : IDbBatch - { - private readonly IDb db; - private readonly IDbBatch batch; - private Dictionary cache; - - public ReadWriteBatch(IDb db) - { - this.db = db; - this.batch = db.GetWriteBatch(); - this.cache = new Dictionary(new ByteArrayComparer()); - } - - /// - /// Records a value that will be written to the database when the method is invoked. - /// - /// The table that will be updated. - /// The table key that identifies the value to be updated. - /// The value to be written to the table. - /// This class for fluent operations. - public IDbBatch Put(byte table, byte[] key, byte[] value) - { - this.cache[new byte[] { table }.Concat(key).ToArray()] = value; - return this.batch.Put(table, key, value); - } - - /// - /// Records a key that will be deleted from the database when the method is invoked. - /// - /// The table that will be updated. - /// The table key that will be removed. - /// This interface for fluent operations. - public IDbBatch Delete(byte table, byte[] key) - { - this.cache[new byte[] { table }.Concat(key).ToArray()] = null; - return this.batch.Delete(table, key); - } - - /// - /// Returns any changes that have been made to the batch or retrieves the value from the underlying database.. - /// - /// The table of the value to be retrieved. - /// The table key of the value to retrieve. - /// This interface for fluent operations. - public byte[] Get(byte table, byte[] key) - { - if (this.cache.TryGetValue(new byte[] { table }.Concat(key).ToArray(), out byte[] value)) - return value; - - return this.db.Get(table, key); - } - - /// - /// Writes the recorded changes to the database. - /// - public void Write() - { - this.batch.Write(); - } - - public void Dispose() - { - this.batch.Dispose(); - } - } - - /// - /// Extension methods that build on the interface. - /// - public static class IDbExt - { - /// - /// Gets a . - /// - /// The database to get the batch for. - /// The . - public static ReadWriteBatch GetReadWriteBatch(this IDb db) - { - return new ReadWriteBatch(db); - } - } - /// /// An iterator that can be used to iterate the keys and values in an compliant database. /// @@ -209,109 +123,4 @@ public interface IDbIterator : IDisposable /// The value. byte[] Value(); } - - /// - /// Extension methods that build on the interface. - /// - public static class IDbIteratorExt - { - private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); - - /// - /// Gets all the keys in the relevant table subject to any supplied constraints. - /// - /// The iterator that also identifies the table being iterated. - /// Defaults to false. Set to true if values should be ommitted - i.e. set to null. - /// Defaults to true. Set to false to return keys in ascending order. - /// Can be set optionally to specify the lower bound of keys to return. - /// Can be set optionally to specify the upper bound of keys to return. - /// Defaults to true. Set to false to omit the key specified in . - /// Defaults to true. Set to false to omit the key specified in . - /// An enumeration containing all the keys and values according to the specified constraints. - public static IEnumerable<(byte[], byte[])> GetAll(this IDbIterator iterator, bool keysOnly = false, bool ascending = true, - byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) - { - bool done = false; - Func breakLoop; - Action next; - - if (!ascending) - { - // Seek to the last key if it was provided. - if (lastKey == null) - iterator.SeekToLast(); - else - { - iterator.Seek(lastKey); - if (!(includeLastKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), lastKey))) - iterator.Prev(); - } - - breakLoop = (firstKey == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, firstKey); - if (compareResult <= 0) - { - // If this is the first key and its not included or we've overshot the range then stop without yielding a value. - if (!includeFirstKey || compareResult < 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Prev(); - } - else /* Ascending */ - { - // Seek to the first key if it was provided. - if (firstKey == null) - iterator.Seek(new byte[0]); - else - { - iterator.Seek(firstKey); - if (!(includeFirstKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), firstKey))) - iterator.Next(); - } - - breakLoop = (lastKey == null) ? (Func)null : (keyBytes) => - { - int compareResult = byteArrayComparer.Compare(keyBytes, lastKey); - if (compareResult >= 0) - { - // If this is the last key and its not included or we've overshot the range then stop without yielding a value. - if (!includeLastKey || compareResult > 0) - return true; - - // Stop after yielding the value. - done = true; - } - - // Keep going. - return false; - }; - - next = () => iterator.Next(); - } - - while (iterator.IsValid()) - { - byte[] keyBytes = iterator.Key(); - - if (breakLoop != null && breakLoop(keyBytes)) - break; - - yield return (keyBytes, keysOnly ? null : iterator.Value()); - - if (done) - break; - - next(); - } - } - } } diff --git a/src/Stratis.Bitcoin/Database/IDbIteratorExt.cs b/src/Stratis.Bitcoin/Database/IDbIteratorExt.cs new file mode 100644 index 0000000000..b84a330aa8 --- /dev/null +++ b/src/Stratis.Bitcoin/Database/IDbIteratorExt.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using NBitcoin; + +namespace Stratis.Bitcoin.Database +{ + /// + /// Extension methods that build on the interface. + /// + public static class IDbIteratorExt + { + private static ByteArrayComparer byteArrayComparer = new ByteArrayComparer(); + + /// + /// Gets all the keys in the relevant table subject to any supplied constraints. + /// + /// The iterator that also identifies the table being iterated. + /// Defaults to false. Set to true if values should be ommitted - i.e. set to null. + /// Defaults to true. Set to false to return keys in ascending order. + /// Can be set optionally to specify the lower bound of keys to return. + /// Can be set optionally to specify the upper bound of keys to return. + /// Defaults to true. Set to false to omit the key specified in . + /// Defaults to true. Set to false to omit the key specified in . + /// An enumeration containing all the keys and values according to the specified constraints. + public static IEnumerable<(byte[], byte[])> GetAll(this IDbIterator iterator, bool keysOnly = false, bool ascending = true, + byte[] firstKey = null, byte[] lastKey = null, bool includeFirstKey = true, bool includeLastKey = true) + { + bool done = false; + Func breakLoop; + Action next; + + if (!ascending) + { + // Seek to the last key if it was provided. + if (lastKey == null) + iterator.SeekToLast(); + else + { + iterator.Seek(lastKey); + if (!(includeLastKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), lastKey))) + iterator.Prev(); + } + + breakLoop = (firstKey == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, firstKey); + if (compareResult <= 0) + { + // If this is the first key and its not included or we've overshot the range then stop without yielding a value. + if (!includeFirstKey || compareResult < 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Prev(); + } + else /* Ascending */ + { + // Seek to the first key if it was provided. + if (firstKey == null) + iterator.Seek(new byte[0]); + else + { + iterator.Seek(firstKey); + if (!(includeFirstKey && iterator.IsValid() && byteArrayComparer.Equals(iterator.Key(), firstKey))) + iterator.Next(); + } + + breakLoop = (lastKey == null) ? (Func)null : (keyBytes) => + { + int compareResult = byteArrayComparer.Compare(keyBytes, lastKey); + if (compareResult >= 0) + { + // If this is the last key and its not included or we've overshot the range then stop without yielding a value. + if (!includeLastKey || compareResult > 0) + return true; + + // Stop after yielding the value. + done = true; + } + + // Keep going. + return false; + }; + + next = () => iterator.Next(); + } + + while (iterator.IsValid()) + { + byte[] keyBytes = iterator.Key(); + + if (breakLoop != null && breakLoop(keyBytes)) + break; + + yield return (keyBytes, keysOnly ? null : iterator.Value()); + + if (done) + break; + + next(); + } + } + } +} diff --git a/src/Stratis.Bitcoin/Database/ReadWriteBatch.cs b/src/Stratis.Bitcoin/Database/ReadWriteBatch.cs new file mode 100644 index 0000000000..4843eb969e --- /dev/null +++ b/src/Stratis.Bitcoin/Database/ReadWriteBatch.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.Linq; +using NBitcoin; + +namespace Stratis.Bitcoin.Database +{ + /// + /// A batch that can be used to record changes that can be applied atomically. + /// + /// The supplied method will immediately reflect any changes that have + /// been made or retrieve the value from the underlying database. In contrast the database method + /// will only show the changes after the method is called. + public class ReadWriteBatch : IDbBatch + { + private readonly IDb db; + private readonly IDbBatch batch; + private Dictionary cache; + + public ReadWriteBatch(IDb db) + { + this.db = db; + this.batch = db.GetWriteBatch(); + this.cache = new Dictionary(new ByteArrayComparer()); + } + + /// + /// Records a value that will be written to the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that identifies the value to be updated. + /// The value to be written to the table. + /// This class for fluent operations. + public IDbBatch Put(byte table, byte[] key, byte[] value) + { + this.cache[new byte[] { table }.Concat(key).ToArray()] = value; + return this.batch.Put(table, key, value); + } + + /// + /// Records a key that will be deleted from the database when the method is invoked. + /// + /// The table that will be updated. + /// The table key that will be removed. + /// This interface for fluent operations. + public IDbBatch Delete(byte table, byte[] key) + { + this.cache[new byte[] { table }.Concat(key).ToArray()] = null; + return this.batch.Delete(table, key); + } + + /// + /// Returns any changes that have been made to the batch or retrieves the value from the underlying database.. + /// + /// The table of the value to be retrieved. + /// The table key of the value to retrieve. + /// This interface for fluent operations. + public byte[] Get(byte table, byte[] key) + { + if (this.cache.TryGetValue(new byte[] { table }.Concat(key).ToArray(), out byte[] value)) + return value; + + return this.db.Get(table, key); + } + + /// + /// Writes the recorded changes to the database. + /// + public void Write() + { + this.batch.Write(); + } + + public void Dispose() + { + this.batch.Dispose(); + } + } + + /// + /// Extension methods that build on the interface. + /// + public static class IDbExt + { + /// + /// Gets a . + /// + /// The database to get the batch for. + /// The . + public static ReadWriteBatch GetReadWriteBatch(this IDb db) + { + return new ReadWriteBatch(db); + } + } +} From 89da0c205518fe900378993526a4d25bdf529dbd Mon Sep 17 00:00:00 2001 From: quantumagi Date: Tue, 12 Jul 2022 18:32:45 +1000 Subject: [PATCH 42/42] Remove whitespace --- src/Stratis.Bitcoin/Database/IDb.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stratis.Bitcoin/Database/IDb.cs b/src/Stratis.Bitcoin/Database/IDb.cs index 83f727b015..807271a29e 100644 --- a/src/Stratis.Bitcoin/Database/IDb.cs +++ b/src/Stratis.Bitcoin/Database/IDb.cs @@ -123,4 +123,4 @@ public interface IDbIterator : IDisposable /// The value. byte[] Value(); } -} +} \ No newline at end of file