From 9ae3989f80d9bff78ed36dd110fed3ba7686dd3b Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 16 Dec 2018 21:54:57 +1100 Subject: [PATCH 01/15] Make it easier to track when transfer statuses are updated --- .../Interfaces/ICrossChainTransfer.cs | 7 +++++++ .../TargetChain/CrossChainTransfer.cs | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs index 25188494..64acc38a 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs @@ -51,6 +51,7 @@ public interface ICrossChainTransfer : IBitcoinSerializable int? BlockHeight { get; } CrossChainTransferStatus Status { get; } + CrossChainTransferStatus? DbStatus { get; } /// /// Depending on the status some fields can't be null. @@ -78,5 +79,11 @@ public interface ICrossChainTransfer : IBitcoinSerializable /// /// Partial transaction. void SetPartialTransaction(Transaction partialTransaction); + + /// + /// Used by the store to note down the status as recorded in the database. + /// This allows the store to update its internal deposit-by-status lookup. + /// + void RecordDbStatus(); } } \ No newline at end of file diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransfer.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransfer.cs index 1b5eda65..0b7eb755 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransfer.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransfer.cs @@ -42,6 +42,10 @@ public class CrossChainTransfer : ICrossChainTransfer public CrossChainTransferStatus Status => this.status; private CrossChainTransferStatus status; + /// + public CrossChainTransferStatus? DbStatus => this.dbStatus; + private CrossChainTransferStatus? dbStatus; + /// /// Parameter-less constructor for (de)serialization. /// @@ -186,6 +190,12 @@ public static bool TemplatesMatch(Transaction partialTransaction1, Transaction p return true; } + /// + public void RecordDbStatus() + { + this.dbStatus = this.status; + } + /// public void CombineSignatures(TransactionBuilder builder, Transaction[] partialTransactions) { @@ -207,5 +217,13 @@ public void SetPartialTransaction(Transaction partialTransaction) { this.partialTransaction = partialTransaction; } + + public override string ToString() + { + return string.Format("DepositId: '{0}', DepositHeight: {1}, Status: '{2}', TransactionHash: '{3}'" + + this.depositTransactionId, this.depositHeight, this.status, this.partialTransaction?.GetHash()) + + ((this.status != CrossChainTransferStatus.SeenInBlock) ? "" : + string.Format(", BlockHeight: {0}, BlockHash: '{1}'", this.blockHeight, this.blockHash)); + } } } From 5aadf2a3ce6bcca76b813f3f63a5dd41fdf07797 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 16 Dec 2018 21:56:33 +1100 Subject: [PATCH 02/15] Add transaction class and supporting classes, interfaces. --- .../Interfaces/IUpdateLookups.cs | 9 + .../TargetChain/CrossChainDBTransaction.cs | 171 ++++++++++++++++++ .../TargetChain/StatusChangeTracker.cs | 3 +- 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs create mode 100644 src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs new file mode 100644 index 00000000..02b8f5a4 --- /dev/null +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs @@ -0,0 +1,9 @@ +using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; + +namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces +{ + public interface ICrossChainLookups + { + void UpdateLookups(StatusChangeTracker tracker); + } +} diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs new file mode 100644 index 00000000..11ad6a79 --- /dev/null +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using DBreeze; +using DBreeze.DataTypes; +using NBitcoin; +using Stratis.Bitcoin.Utilities; +using Stratis.FederatedPeg.Features.FederationGateway.Interfaces; + +namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain +{ + public enum CrossChainTransactionMode + { + Read, + ReadWrite + } + + public class CrossChainDBTransaction : IDisposable + { + private readonly Network network; + private DBreeze.Transactions.Transaction transaction; + private readonly ICrossChainLookups crossChainLookups; + private readonly CrossChainTransactionMode mode; + private StatusChangeTracker tracker; + + // TODO: We need the network argument due to a shortcoming/inconsistency in our DBreeze serialization. + private CrossChainDBTransaction(DBreeze.Transactions.Transaction transaction, Network network, ICrossChainLookups updateLookups, CrossChainTransactionMode mode) + { + this.transaction = transaction; + this.network = network; + this.crossChainLookups = updateLookups; + this.mode = mode; + + transaction.ValuesLazyLoadingIsOn = false; + + if (mode == CrossChainTransactionMode.ReadWrite) + { + transaction.SynchronizeTables(CrossChainDB.TransferTableName, CrossChainDB.CommonTableName); + } + + this.tracker = new StatusChangeTracker(); + } + + public static CrossChainDBTransaction GetTransaction(DBreezeEngine dBreezeEngine, Network network, ICrossChainLookups updateLookups, CrossChainTransactionMode mode) + { + return new CrossChainDBTransaction(dBreezeEngine.GetTransaction(eTransactionTablesLockTypes.EXCLUSIVE), network, updateLookups, mode); + } + + public ICrossChainTransfer GetTransfer(uint256 depositId) + { + Row transferRow = this.transaction.Select(CrossChainDB.TransferTableName, depositId.ToBytes()); + + if (transferRow.Exists) + { + // Workaround for shortcoming in DBreeze serialization. + var crossChainTransfer = new CrossChainTransfer(); + crossChainTransfer.FromBytes(transferRow.Value, this.network.Consensus.ConsensusFactory); + crossChainTransfer.RecordDbStatus(); + + return crossChainTransfer; + } + + return null; + } + + public IEnumerable EnumerateTransfers() + { + foreach (Row transferRow in this.transaction.SelectForward(CrossChainDB.TransferTableName)) + { + // Workaround for shortcoming in DBreeze serialization. + var crossChainTransfer = new CrossChainTransfer(); + crossChainTransfer.FromBytes(transferRow.Value, this.network.Consensus.ConsensusFactory); + crossChainTransfer.RecordDbStatus(); + + yield return crossChainTransfer; + } + } + + public void PutTransfer(ICrossChainTransfer transfer) + { + Guard.Assert(this.mode != CrossChainTransactionMode.Read); + + // Record the old status + this.tracker[transfer] = transfer.DbStatus; + + // Write the transfer. + this.transaction.Insert(CrossChainDB.TransferTableName, transfer.DepositTransactionId.ToBytes(), transfer); + } + + public void DeleteTransfer(ICrossChainTransfer transfer) + { + Guard.NotNull(transfer, nameof(transfer)); + + // Only transfers that exist in the db purely due to being seen in a block will be removed. + Guard.Assert(transfer.DepositHeight == null); + + this.tracker[transfer] = transfer.DbStatus; + + this.transaction.RemoveKey(CrossChainDB.TransferTableName, transfer.DepositTransactionId.ToBytes()); + } + + public void Commit() + { + Guard.Assert(this.mode != CrossChainTransactionMode.Read); + + this.transaction.Commit(); + this.crossChainLookups.UpdateLookups(this.tracker); + } + + public void Rollback() + { + Guard.Assert(this.mode != CrossChainTransactionMode.Read); + + this.transaction.Rollback(); + } + + public BlockLocator LoadTipHashAndHeight() + { + var blockLocator = new BlockLocator(); + try + { + Row row = this.transaction.Select(CrossChainDB.CommonTableName, CrossChainDB.RepositoryTipKey); + Guard.Assert(row.Exists); + blockLocator.FromBytes(row.Value); + } + catch (Exception) + { + blockLocator.Blocks = new List { this.network.GenesisHash }; + } + + return blockLocator; + + } + + public void SaveTipHashAndHeight(BlockLocator blockLocator) + { + Guard.Assert(this.mode != CrossChainTransactionMode.Read); + + this.transaction.Insert(CrossChainDB.CommonTableName, CrossChainDB.RepositoryTipKey, blockLocator.ToBytes()); + } + + public int? LoadNextMatureHeight() + { + Row row = this.transaction.Select(CrossChainDB.CommonTableName, CrossChainDB.NextMatureTipKey); + + return (row?.Exists ?? false) ? row.Value : (int?)null; + } + + public void SaveNextMatureHeight(int newTip) + { + Guard.Assert(this.mode != CrossChainTransactionMode.Read); + + this.transaction.Insert(CrossChainDB.CommonTableName, CrossChainDB.NextMatureTipKey, newTip); + } + + /// A string to identify this transaction by. + /// A concatenation of the creation time and thread id. + public override string ToString() + { + return string.Format("{0}({1})", this.transaction.CreatedUdt.GetHashCode(), this.transaction.ManagedThreadId); + } + + public void Dispose() + { + if (this.transaction != null) + { + this.transaction.Dispose(); + this.transaction = null; + } + } + } +} diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs index 36877734..ff55ff45 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs @@ -39,7 +39,8 @@ public void SetTransferStatus(ICrossChainTransfer transfer, CrossChainTransferSt /// A list of unique block hashes for the transfers being tracked. public uint256[] UniqueBlockHashes() { - return this.Keys.Where(k => k.BlockHash != null).Select(k => k.BlockHash).Distinct().ToArray(); + // This tests for transfers containing block hashes and checks that this is not a deletion. + return this.Keys.Where(k => k.BlockHash != null && (k.DepositHeight != null || k.DbStatus == null)).Select(k => k.BlockHash).Distinct().ToArray(); } } } From 93ad2589eccf66083225355b5e7acf785a907827 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 16 Dec 2018 21:57:08 +1100 Subject: [PATCH 03/15] Add low-level db access class for use by store --- .../Interfaces/ICrossChainDB.cs | 24 ++ .../TargetChain/CrossChainDB.cs | 359 ++++++++++++++++++ 2 files changed, 383 insertions(+) create mode 100644 src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs create mode 100644 src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs new file mode 100644 index 00000000..63df72a2 --- /dev/null +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs @@ -0,0 +1,24 @@ +using System; +using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; + +namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces +{ + public interface ICrossChainDB : IDisposable + { + /// Initializes the cross-chain-transfer store. + void Initialize(); + + /// + /// Creates a for either read or read/write operations. + /// + /// The mode which is either + /// or + /// The object. + CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read); + + /// Updates the internal lookups based on the changes recorded in the tracker object. + /// Information about how to update the lookups. + /// This method should is only intended be called by the class. + void UpdateLookups(StatusChangeTracker tracker); + } +} diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs new file mode 100644 index 00000000..b0fa9047 --- /dev/null +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using DBreeze; +using DBreeze.Utils; +using Microsoft.Extensions.Logging; +using NBitcoin; +using Stratis.Bitcoin.Configuration; +using Stratis.Bitcoin.Utilities; +using Stratis.FederatedPeg.Features.FederationGateway.Interfaces; + +namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain +{ + /// + /// This class provided the low-level cross-chain database functionality which + /// includes maintaining the various lookups in a transactional manner. + /// Also see . + /// + public class CrossChainDB : ICrossChainDB + { + /// This table contains the cross-chain transfer information. + public const string TransferTableName = "Transfers"; + + /// This table keeps track of the chain tips so that we know exactly what data our transfer table contains. + public const string CommonTableName = "Common"; + + /// Instance logger. + private readonly ILogger logger; + + /// The network. + private readonly Network network; + + /// The chain. + private readonly ConcurrentChain chain; + + /// This contains deposits ids indexed by block hash of the corresponding transaction. + protected readonly Dictionary> depositIdsByBlockHash = new Dictionary>(); + + /// This contains the block heights by block hashes for only the blocks of interest in our chain. + protected readonly Dictionary blockHeightsByBlockHash = new Dictionary(); + + /// This table contains deposits ids by status. + protected readonly Dictionary> depositsIdsByStatus = new Dictionary>(); + + /// The block height on the counter-chain for which the next list of deposits is expected. + public int NextMatureDepositHeight { get; protected set; } + + /// The tip of our chain when we last updated the store. + public ChainedHeader TipHashAndHeight { get; protected set; } + + /// The key of the repository tip in the common table. + public static readonly byte[] RepositoryTipKey = new byte[] { 0 }; + + /// The key of the counter-chain last mature block tip in the common table. + public static readonly byte[] NextMatureTipKey = new byte[] { 1 }; + + /// Access to DBreeze database. + private readonly DBreezeEngine DBreeze; + + /// + /// Constructs the class controlling the underlying DBreeze database. + /// + /// The network type of the transaction recorded in the database. + /// The logger facrtory used to create the logger. + /// The concurrent chain associated with the objects in the database. + /// The datafolder where the database files will be persisted. + /// Used to identify the MultiSigAddress that the database is for. + public CrossChainDB( + Network network, + ILoggerFactory loggerFactory, + ConcurrentChain chain, + DataFolder dataFolder, + IFederationGatewaySettings federationGatewaySettings) + { + Guard.NotNull(network, nameof(network)); + Guard.NotNull(loggerFactory, nameof(loggerFactory)); + Guard.NotNull(chain, nameof(chain)); + Guard.NotNull(dataFolder, nameof(dataFolder)); + Guard.NotNull(federationGatewaySettings, nameof(federationGatewaySettings)); + + this.network = network; + this.logger = loggerFactory.CreateLogger(this.GetType().FullName); + this.chain = chain; + + Block genesis = network.GetGenesis(); + this.TipHashAndHeight = new ChainedHeader(genesis.Header, genesis.GetHash(), 0); + this.NextMatureDepositHeight = 1; + + // Future-proof store name. + string depositStoreName = "federatedTransfers" + federationGatewaySettings.MultiSigAddress.ToString(); + string folder = Path.Combine(dataFolder.RootPath, depositStoreName); + Directory.CreateDirectory(folder); + this.DBreeze = new DBreezeEngine(folder); + + // Initialize tracking deposits by status. + foreach (object status in typeof(CrossChainTransferStatus).GetEnumValues()) + this.depositsIdsByStatus[(CrossChainTransferStatus)status] = new HashSet(); + } + + /// + public void Initialize() + { + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction()) + { + this.LoadTipHashAndHeight(xdbTransaction); + this.LoadNextMatureHeight(xdbTransaction); + this.logger.LogTrace("Loaded TipHashAndHeight {0} and NextMatureDepositHeight {1}.", this.TipHashAndHeight, this.NextMatureDepositHeight); + + // Initialize the lookups. + foreach (ICrossChainTransfer transfer in xdbTransaction.EnumerateTransfers()) + { + this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); + + if (transfer.BlockHash != null && transfer.BlockHeight != null) + { + if (!this.depositIdsByBlockHash.TryGetValue(transfer.BlockHash, out HashSet deposits)) + { + deposits = new HashSet(); + this.depositIdsByBlockHash[transfer.BlockHash] = deposits; + } + + deposits.Add(transfer.DepositTransactionId); + + this.blockHeightsByBlockHash[transfer.BlockHash] = (int)transfer.BlockHeight; + } + } + this.logger.LogTrace("Lookups initialised."); + } + + this.logger.LogTrace("(-)"); + } + + /// + public CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read) + { + CrossChainDBTransaction xdbTransaction = CrossChainDBTransaction.GetTransaction(this.DBreeze, this.network, (ICrossChainLookups)this, mode); + + this.logger.LogTrace("Transaction '{0}' created for {1}.", xdbTransaction, mode); + + return xdbTransaction; + } + + /// + /// Gets an array of objects corresponding to the array + /// of deposit ids passed to the method. + /// + /// The providing the context for the get. + /// The array of deposit ids. + /// An array of objects or null for non-existing transfers. + protected ICrossChainTransfer[] Get(CrossChainDBTransaction xdbTransaction, uint256[] depositIds) + { + try + { + // To boost performance we will access the deposits sorted by deposit id. + var depositDict = new Dictionary(); + for (int i = 0; i < depositIds.Length; i++) + depositDict[depositIds[i]] = i; + + var byteListComparer = new ByteListComparer(); + List> depositList = depositDict.ToList(); + depositList.Sort((pair1, pair2) => byteListComparer.Compare(pair1.Key.ToBytes(), pair2.Key.ToBytes())); + + var res = new ICrossChainTransfer[depositIds.Length]; + + foreach (KeyValuePair kv in depositList) + { + ICrossChainTransfer crossChainTransfer = xdbTransaction.GetTransfer(kv.Key); + if (crossChainTransfer != null) + res[kv.Value] = crossChainTransfer; + } + + this.logger.LogTrace("Transaction '{0}' read {1} transfers.", xdbTransaction, res.Length); + + return res; + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Persist multiple cross-chain transfer information into the database. + /// The crosschain database transaction context to use. + /// Cross-chain transfers to be inserted. + protected void PutTransfers(CrossChainDBTransaction xdbTransaction, ICrossChainTransfer[] crossChainTransfers) + { + try + { + // Optimal ordering for DB consumption. + var byteListComparer = new ByteListComparer(); + List orderedTransfers = crossChainTransfers.ToList(); + orderedTransfers.Sort((pair1, pair2) => byteListComparer.Compare(pair1.DepositTransactionId.ToBytes(), pair2.DepositTransactionId.ToBytes())); + + // Write each transfer in order. + foreach (ICrossChainTransfer transfer in orderedTransfers) + { + xdbTransaction.PutTransfer(transfer); + } + + this.logger.LogTrace("Transaction '{0}' updated {1} transfers.", xdbTransaction, orderedTransfers.Count); + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Rolls back the database if an operation running in the context of a database transaction fails. + /// Database transaction to roll back. + /// Exception to report and re-raise. + /// Short reason/context code of failure. + protected void RollbackAndThrowTransactionError(CrossChainDBTransaction xdbTransaction, Exception exception, string reason = "FAILED_TRANSACTION") + { + this.logger.LogError("Error during database update: {0}", exception.Message); + this.logger.LogTrace("(-):[{0}]", reason); + + xdbTransaction.Rollback(); + throw exception; + } + + /// Loads the tip and hash height. + /// The DBreeze transaction context to use. + /// The hash and height pair. + protected ChainedHeader LoadTipHashAndHeight(CrossChainDBTransaction xdbTransaction) + { + try + { + BlockLocator blockLocator = xdbTransaction.LoadTipHashAndHeight(); + + this.TipHashAndHeight = this.chain.GetBlock(blockLocator.Blocks[0]) ?? this.chain.FindFork(blockLocator); + + this.logger.LogTrace("Transaction '{0}' read TipHashAndHeight {1}.", xdbTransaction, this.TipHashAndHeight); + + return this.TipHashAndHeight; + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Saves the tip and hash height. + /// The crosschain db transaction context to use. + /// The new tip to persist. + protected void SaveTipHashAndHeight(CrossChainDBTransaction xdbTransaction, ChainedHeader newTip) + { + try + { + BlockLocator locator = this.chain.GetBlock(newTip.HashBlock).GetLocator(); + xdbTransaction.SaveTipHashAndHeight(locator); + this.TipHashAndHeight = newTip; + + this.logger.LogTrace("Transaction '{0}' set TipHashAndHeight {1}", xdbTransaction, newTip); + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Loads the counter-chain next mature block height. + /// The crosschain db transaction context to use. + /// The hash and height pair. + protected int LoadNextMatureHeight(CrossChainDBTransaction xdbTransaction) + { + try + { + int height = xdbTransaction.LoadNextMatureHeight() ?? this.NextMatureDepositHeight; + + this.logger.LogTrace("Transaction '{0}' read NextMatureDepositHeight {1}", xdbTransaction, height); + + return height; + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Saves the counter-chain next mature block height. + /// The crosschain db transaction context to use. + /// The next mature block height on the counter-chain. + protected void SaveNextMatureHeight(CrossChainDBTransaction xdbTransaction, int newTip) + { + try + { + xdbTransaction.SaveNextMatureHeight(newTip); + this.NextMatureDepositHeight = newTip; + + this.logger.LogTrace("Transaction '{0}' set NextMatureDepositHeight {1}", newTip); + } + catch (Exception err) + { + this.logger.LogError("Transaction '{0}' failed with '{1}'.", xdbTransaction, err.Message); + throw err; + } + } + + /// Updates the status lookup based on a transfer and its previous status. + /// The cross-chain transfer that was update. + /// The old status. + private void TransferStatusUpdated(ICrossChainTransfer transfer, CrossChainTransferStatus? oldStatus) + { + if (oldStatus != null) + { + this.depositsIdsByStatus[(CrossChainTransferStatus)oldStatus].Remove(transfer.DepositTransactionId); + } + + this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); + } + + /// + public void UpdateLookups(StatusChangeTracker tracker) + { + foreach (uint256 hash in tracker.UniqueBlockHashes()) + { + if (this.depositIdsByBlockHash.ContainsKey(hash)) continue; + this.depositIdsByBlockHash[hash] = new HashSet(); + } + + foreach (KeyValuePair kv in tracker) + { + ICrossChainTransfer transfer = kv.Key; + CrossChainTransferStatus? status = kv.Value; + + if (transfer.DepositHeight == null && transfer.DbStatus != null /* Not new */) + { + // Transfer is being removed. + this.depositsIdsByStatus[transfer.Status].Remove(transfer.DepositTransactionId); + this.depositIdsByBlockHash[transfer.BlockHash].Remove(transfer.DepositTransactionId); + } + else + { + this.TransferStatusUpdated(transfer, status); + + if (transfer.BlockHash != null && transfer.BlockHeight != null) + { + if (!this.depositIdsByBlockHash[transfer.BlockHash].Contains(transfer.DepositTransactionId)) + this.depositIdsByBlockHash[transfer.BlockHash].Add(transfer.DepositTransactionId); + this.blockHeightsByBlockHash[transfer.BlockHash] = (int)transfer.BlockHeight; + } + } + } + + this.logger.LogTrace("Lookups updated from tracker containing {0} items.", tracker.Count); + } + + public virtual void Dispose() + { + this.DBreeze.Dispose(); + } + } +} From e9c633b661172646f54a56bc27aa65bdd8c197cf Mon Sep 17 00:00:00 2001 From: quantumagi Date: Sun, 16 Dec 2018 21:58:14 +1100 Subject: [PATCH 04/15] Simplify store by removing code provided by base and periphery classes --- .../Interfaces/ICrossChainTransferStore.cs | 5 +- .../TargetChain/CrossChainTransferStore.cs | 575 +++++------------- 2 files changed, 146 insertions(+), 434 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs index e0e35cdc..bccb945e 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs @@ -6,11 +6,8 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { /// Interface for interacting with the cross-chain transfer database. - public interface ICrossChainTransferStore : IDisposable + public interface ICrossChainTransferStore : IDisposable, ICrossChainDB, ICrossChainLookups { - /// Initializes the cross-chain-transfer store. - void Initialize(); - /// Starts the cross-chain-transfer store. void Start(); diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index ebaec21f..18568f75 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; -using DBreeze; -using DBreeze.DataTypes; -using DBreeze.Utils; using Microsoft.Extensions.Logging; using NBitcoin; using Stratis.Bitcoin; @@ -18,45 +14,13 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain { - public class CrossChainTransferStore : ICrossChainTransferStore + public class CrossChainTransferStore : CrossChainDB, ICrossChainTransferStore { - /// This table contains the cross-chain transfer information. - private const string transferTableName = "Transfers"; - - /// This table keeps track of the chain tips so that we know exactly what data our transfer table contains. - private const string commonTableName = "Common"; - // Block batch size for synchronization private const int synchronizationBatchSize = 1000; - /// This contains deposits ids indexed by block hash of the corresponding transaction. - private readonly Dictionary> depositIdsByBlockHash = new Dictionary>(); - - /// This contains the block heights by block hashes for only the blocks of interest in our chain. - private readonly Dictionary blockHeightsByBlockHash = new Dictionary(); - - /// This table contains deposits ids by status. - private readonly Dictionary> depositsIdsByStatus = new Dictionary>(); - - /// - public int NextMatureDepositHeight { get; private set; } - - /// - public ChainedHeader TipHashAndHeight { get; private set; } - - public BlockLocator BlockLocator { get; private set; } - - /// The key of the repository tip in the common table. - private static readonly byte[] RepositoryTipKey = new byte[] { 0 }; - - /// The key of the counter-chain last mature block tip in the common table. - private static readonly byte[] NextMatureTipKey = new byte[] { 1 }; - /// Instance logger. private readonly ILogger logger; - - /// Access to DBreeze database. - private readonly DBreezeEngine DBreeze; private readonly Network network; private readonly ConcurrentChain chain; private readonly IWithdrawalExtractor withdrawalExtractor; @@ -73,6 +37,7 @@ public class CrossChainTransferStore : ICrossChainTransferStore public CrossChainTransferStore(Network network, DataFolder dataFolder, ConcurrentChain chain, IFederationGatewaySettings settings, IDateTimeProvider dateTimeProvider, ILoggerFactory loggerFactory, IWithdrawalExtractor withdrawalExtractor, IFullNode fullNode, IBlockRepository blockRepository, IFederationWalletManager federationWalletManager, IFederationWalletTransactionHandler federationWalletTransactionHandler) + : base(network, loggerFactory, chain, dataFolder, settings) { Guard.NotNull(network, nameof(network)); Guard.NotNull(dataFolder, nameof(dataFolder)); @@ -99,56 +64,6 @@ public CrossChainTransferStore(Network network, DataFolder dataFolder, Concurren this.TipHashAndHeight = this.chain.GetBlock(0); this.NextMatureDepositHeight = 1; this.cancellation = new CancellationTokenSource(); - - // Future-proof store name. - string depositStoreName = "federatedTransfers" + settings.MultiSigAddress.ToString(); - string folder = Path.Combine(dataFolder.RootPath, depositStoreName); - Directory.CreateDirectory(folder); - this.DBreeze = new DBreezeEngine(folder); - - // Initialize tracking deposits by status. - foreach (object status in typeof(CrossChainTransferStatus).GetEnumValues()) - this.depositsIdsByStatus[(CrossChainTransferStatus)status] = new HashSet(); - } - - /// Performs any needed initialisation for the database. - public void Initialize() - { - lock (this.lockObj) - { - this.logger.LogTrace("()"); - - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) - { - dbreezeTransaction.ValuesLazyLoadingIsOn = false; - - this.LoadTipHashAndHeight(dbreezeTransaction); - this.LoadNextMatureHeight(dbreezeTransaction); - - // Initialize the lookups. - foreach (Row transferRow in dbreezeTransaction.SelectForward(transferTableName)) - { - var transfer = new CrossChainTransfer(); - transfer.FromBytes(transferRow.Value, this.network.Consensus.ConsensusFactory); - this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); - - if (transfer.BlockHash != null && transfer.BlockHeight != null) - { - if (!this.depositIdsByBlockHash.TryGetValue(transfer.BlockHash, out HashSet deposits)) - { - deposits = new HashSet(); - this.depositIdsByBlockHash[transfer.BlockHash] = deposits; - } - - deposits.Add(transfer.DepositTransactionId); - - this.blockHeightsByBlockHash[transfer.BlockHash] = (int)transfer.BlockHeight; - } - } - } - - this.logger.LogTrace("(-)"); - } } /// Starts the cross-chain-transfer store. @@ -163,11 +78,15 @@ public void Start() // suspended due to the missing wallet transactions which will rewind the counter- // chain tip to then reprocess them. if (this.federationWalletManager.RemoveTransientTransactions()) + { + this.logger.LogTrace("Unseen transactions have been removed from the wallet."); this.federationWalletManager.SaveWallet(); + } Guard.Assert(this.Synchronize()); - // Any transactions seen in blocks must also be present in the wallet. + this.logger.LogTrace("Adding any missing but seen transactions to wallet."); + FederationWallet wallet = this.federationWalletManager.GetWallet(); ICrossChainTransfer[] transfers = Get(this.depositsIdsByStatus[CrossChainTransferStatus.SeenInBlock].ToArray()); foreach (ICrossChainTransfer transfer in transfers) @@ -242,12 +161,6 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] if (partialTransfer.Status != CrossChainTransferStatus.Partial && partialTransfer.Status != CrossChainTransferStatus.FullySigned) continue; - if (partialTransfer.DepositHeight != null && partialTransfer.DepositHeight >= this.NextMatureDepositHeight) - { - //tracker.SetTransferStatus(partialTransfer, CrossChainTransferStatus.Suspended); - continue; - } - List<(Transaction, TransactionData, IWithdrawal)> walletData = this.federationWalletManager.FindWithdrawalTransactions(partialTransfer.DepositTransactionId); if (walletData.Count == 1 && ValidateTransaction(walletData[0].Item1)) { @@ -257,6 +170,9 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] if (CrossChainTransfer.TemplatesMatch(walletTran, partialTransfer.PartialTransaction)) { + this.logger.LogTrace("Could not find transaction by hash {0} but found it by matching template.", partialTransfer.PartialTransaction.GetHash()); + this.logger.LogTrace("Will update transfer with wallet transaction {0}.", walletTran.GetHash()); + partialTransfer.SetPartialTransaction(walletTran); if (walletData[0].Item2.BlockHeight != null) @@ -272,38 +188,43 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] // The chain may have been rewound so that this transaction or its UTXO's have been lost. // Rewind our recorded chain A tip to ensure the transaction is re-built once UTXO's become available. - if (partialTransfer.DepositHeight < newChainATip) - newChainATip = partialTransfer.DepositHeight ?? newChainATip; + if (partialTransfer.DepositHeight != null && partialTransfer.DepositHeight < newChainATip) + { + newChainATip = (int)partialTransfer.DepositHeight; + + this.logger.LogTrace("Will rewind NextMatureDepositHeight due to suspended deposit {0} at height {1}.", + partialTransfer.DepositTransactionId, newChainATip); + } tracker.SetTransferStatus(partialTransfer, CrossChainTransferStatus.Suspended); } + // Exit if nothing to do. if (tracker.Count == 0) return crossChainTransfers; - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - int oldChainATip = this.NextMatureDepositHeight; try { foreach (KeyValuePair kv in tracker) { - this.PutTransfer(dbreezeTransaction, kv.Key); + xdbTransaction.PutTransfer(kv.Key); } - this.SaveNextMatureHeight(dbreezeTransaction, newChainATip); - dbreezeTransaction.Commit(); - this.UpdateLookups(tracker); + xdbTransaction.SaveNextMatureHeight(newChainATip); + xdbTransaction.Commit(); + + bool walletUpdated = false; // Remove any remnants of suspended transactions from the wallet. foreach (KeyValuePair kv in tracker) { if (kv.Value == CrossChainTransferStatus.Suspended) { - this.federationWalletManager.RemoveTransientTransactions(kv.Key.DepositTransactionId); + walletUpdated |= this.federationWalletManager.RemoveTransientTransactions(kv.Key.DepositTransactionId); } } @@ -312,11 +233,12 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] { if (t.Item3.BlockNumber >= newChainATip) { - this.federationWalletManager.RemoveTransientTransactions(t.Item3.DepositId); + walletUpdated |= this.federationWalletManager.RemoveTransientTransactions(t.Item3.DepositId); } } - this.federationWalletManager.SaveWallet(); + if (walletUpdated) + this.federationWalletManager.SaveWallet(); return crossChainTransfers; } @@ -325,7 +247,7 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] // Restore expected store state in case the calling code retries / continues using the store. this.NextMatureDepositHeight = oldChainATip; - this.RollbackAndThrowTransactionError(dbreezeTransaction, err, "SANITY_ERROR"); + this.RollbackAndThrowTransactionError(xdbTransaction, err, "SANITY_ERROR"); // Dummy return as the above method throws. Avoids compiler error. return null; @@ -337,8 +259,6 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, Recipient r { try { - this.logger.LogTrace("()"); - // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; @@ -356,7 +276,11 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, Recipient r // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); - this.logger.LogTrace("(-)"); + if (transaction == null) + this.logger.LogTrace("Failed to create deterministic transaction."); + else + this.logger.LogTrace("Deterministic transaction created."); + return transaction; } catch (Exception error) @@ -367,19 +291,6 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, Recipient r return null; } - /// Rolls back the database if an operation running in the context of a database transaction fails. - /// Database transaction to roll back. - /// Exception to report and re-raise. - /// Short reason/context code of failure. - private void RollbackAndThrowTransactionError(DBreeze.Transactions.Transaction dbreezeTransaction, Exception exception, string reason = "FAILED_TRANSACTION") - { - this.logger.LogError("Error during database update: {0}", exception.Message); - this.logger.LogTrace("(-):[{0}]", reason); - - dbreezeTransaction.Rollback(); - throw exception; - } - /// public Task SaveCurrentTipAsync() { @@ -387,11 +298,10 @@ public Task SaveCurrentTipAsync() { lock (this.lockObj) { - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - this.SaveNextMatureHeight(dbreezeTransaction, this.NextMatureDepositHeight); - dbreezeTransaction.Commit(); + xdbTransaction.SaveNextMatureHeight(this.NextMatureDepositHeight); + xdbTransaction.Commit(); } } }); @@ -419,12 +329,14 @@ public Task RecordLatestMatureDepositsAsync(IMaturedBlockDeposits[] mature if (maturedBlockDeposits.Length == 0 || maturedBlockDeposits.First().Block.BlockHeight != this.NextMatureDepositHeight) { + this.logger.LogTrace("No block found starting at height {0}.", this.NextMatureDepositHeight); this.logger.LogTrace("(-):[NO_VIABLE_BLOCKS]"); return true; } if (maturedBlockDeposits.Last().Block.BlockHeight != this.NextMatureDepositHeight + maturedBlockDeposits.Length - 1) { + this.logger.LogTrace("Input containing duplicate blocks will be ignored."); this.logger.LogTrace("(-):[DUPLICATE_BLOCKS]"); return true; } @@ -490,7 +402,8 @@ public Task RecordLatestMatureDepositsAsync(IMaturedBlockDeposits[] mature if (transaction != null) { - // Reserve the UTXOs before building the next transaction. + this.logger.LogTrace("Reserving the UTXOs before building the next transaction."); + walletUpdated |= this.federationWalletManager.ProcessTransaction(transaction, isPropagated: false); status = CrossChainTransferStatus.Partial; @@ -513,10 +426,8 @@ public Task RecordLatestMatureDepositsAsync(IMaturedBlockDeposits[] mature } } - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - int currentDepositHeight = this.NextMatureDepositHeight; try @@ -529,22 +440,26 @@ public Task RecordLatestMatureDepositsAsync(IMaturedBlockDeposits[] mature // Update new or modified transfers. foreach (KeyValuePair kv in tracker) { - this.PutTransfer(dbreezeTransaction, kv.Key); + ICrossChainTransfer transfer = kv.Key; + + this.logger.LogTrace("Registering transfer: {0}.", transfer); + + xdbTransaction.PutTransfer(transfer); } // Ensure we get called for a retry by NOT advancing the chain A tip if the block // contained any suspended transfers. if (!haveSuspendedTransfers) { - this.SaveNextMatureHeight(dbreezeTransaction, this.NextMatureDepositHeight + 1); + this.SaveNextMatureHeight(xdbTransaction, this.NextMatureDepositHeight + 1); } - dbreezeTransaction.Commit(); - this.UpdateLookups(tracker); + xdbTransaction.Commit(); } catch (Exception err) { - // Undo reserved UTXO's. + this.logger.LogTrace("Undoing reserved UTXOs."); + if (walletUpdated) { foreach (KeyValuePair kv in tracker) @@ -560,7 +475,7 @@ public Task RecordLatestMatureDepositsAsync(IMaturedBlockDeposits[] mature // Restore expected store state in case the calling code retries / continues using the store. this.NextMatureDepositHeight = currentDepositHeight; - this.RollbackAndThrowTransactionError(dbreezeTransaction, err, "DEPOSIT_ERROR"); + this.RollbackAndThrowTransactionError(xdbTransaction, err, "DEPOSIT_ERROR"); } } } @@ -612,12 +527,10 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran return transfer.PartialTransaction; } - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { try { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - this.federationWalletManager.ProcessTransaction(transfer.PartialTransaction); this.federationWalletManager.SaveWallet(); @@ -626,11 +539,8 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran transfer.SetStatus(CrossChainTransferStatus.FullySigned); } - this.PutTransfer(dbreezeTransaction, transfer); - dbreezeTransaction.Commit(); - - // Do this last to maintain DB integrity. We are assuming that this won't throw. - this.TransferStatusUpdated(transfer, CrossChainTransferStatus.Partial); + xdbTransaction.PutTransfer(transfer); + xdbTransaction.Commit(); } catch (Exception err) { @@ -638,7 +548,7 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran transfer.SetPartialTransaction(oldTransaction); this.federationWalletManager.ProcessTransaction(oldTransaction); this.federationWalletManager.SaveWallet(); - this.RollbackAndThrowTransactionError(dbreezeTransaction, err, "MERGE_ERROR"); + this.RollbackAndThrowTransactionError(xdbTransaction, err, "MERGE_ERROR"); } this.logger.LogTrace("(-)"); @@ -656,7 +566,7 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran /// The blocks used to update the store. Must be sorted by ascending height leading up to the new tip. private void Put(List blocks) { - this.logger.LogTrace("()"); + this.logger.LogTrace("Putting {0} blocks.", blocks.Count); if (blocks.Count == 0) this.logger.LogTrace("(-):0"); @@ -681,7 +591,7 @@ private void Put(List blocks) // Exiting here and saving the tip after the sync. this.TipHashAndHeight = this.chain.GetBlock(blocks.Last().GetHash()); - this.logger.LogTrace("(-)"); + this.logger.LogTrace("(-)[NO_DEPOSITS]"); return; } @@ -693,68 +603,65 @@ private void Put(List blocks) transferLookup[uniqueDepositIds[i]] = uniqueTransfers[i]; } - // Only create a transaction if there is important work to do. - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + // Find transfer transactions in blocks + foreach (Block block in blocks) { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - - ChainedHeader prevTip = this.TipHashAndHeight; + // First check the database to see if we already know about these deposits. + IWithdrawal[] withdrawals = allWithdrawals[block.GetHash()].ToArray(); + ICrossChainTransfer[] crossChainTransfers = withdrawals.Select(d => transferLookup[d.DepositId]).ToArray(); - try + // Update the information about these deposits or record their status. + for (int i = 0; i < crossChainTransfers.Length; i++) { - var tracker = new StatusChangeTracker(); - - // Find transfer transactions in blocks - foreach (Block block in blocks) - { - // First check the database to see if we already know about these deposits. - IWithdrawal[] withdrawals = allWithdrawals[block.GetHash()].ToArray(); - ICrossChainTransfer[] crossChainTransfers = withdrawals.Select(d => transferLookup[d.DepositId]).ToArray(); + IWithdrawal withdrawal = withdrawals[i]; + Transaction transaction = block.Transactions.Single(t => t.GetHash() == withdrawal.Id); - // Update the information about these deposits or record their status. - for (int i = 0; i < crossChainTransfers.Length; i++) - { - IWithdrawal withdrawal = withdrawals[i]; - Transaction transaction = block.Transactions.Single(t => t.GetHash() == withdrawal.Id); + // Ensure that the wallet is in step. + this.federationWalletManager.ProcessTransaction(transaction, withdrawal.BlockNumber, block); - // Ensure that the wallet is in step. - this.federationWalletManager.ProcessTransaction(transaction, withdrawal.BlockNumber, block); + if (crossChainTransfers[i] == null) + { + Script scriptPubKey = BitcoinAddress.Create(withdrawal.TargetAddress, this.network).ScriptPubKey; - if (crossChainTransfers[i] == null) - { - Script scriptPubKey = BitcoinAddress.Create(withdrawal.TargetAddress, this.network).ScriptPubKey; + crossChainTransfers[i] = new CrossChainTransfer(CrossChainTransferStatus.SeenInBlock, withdrawal.DepositId, + scriptPubKey, withdrawal.Amount, null, transaction, withdrawal.BlockHash, withdrawal.BlockNumber); - crossChainTransfers[i] = new CrossChainTransfer(CrossChainTransferStatus.SeenInBlock, withdrawal.DepositId, - scriptPubKey, withdrawal.Amount, null, transaction, withdrawal.BlockHash, withdrawal.BlockNumber); + transferLookup[crossChainTransfers[i].DepositTransactionId] = crossChainTransfers[i]; + } + else + { + crossChainTransfers[i].SetPartialTransaction(transaction); + crossChainTransfers[i].SetStatus(CrossChainTransferStatus.SeenInBlock, withdrawal.BlockHash, withdrawal.BlockNumber); + } + } + } - tracker.SetTransferStatus(crossChainTransfers[i]); - } - else - { - crossChainTransfers[i].SetPartialTransaction(transaction); + // Only create a transaction if there is work to do. + if (transferLookup.Count == 0) + { + this.logger.LogTrace("(-)[NOTHING_TO_DO]"); + return; + } - tracker.SetTransferStatus(crossChainTransfers[i], - CrossChainTransferStatus.SeenInBlock, withdrawal.BlockHash, withdrawal.BlockNumber); - } - } - } + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + { + ChainedHeader prevTip = this.TipHashAndHeight; + try + { // Write transfers. - this.PutTransfers(dbreezeTransaction, tracker.Keys.ToArray()); + this.PutTransfers(xdbTransaction, transferLookup.Select(x => x.Value).ToArray()); // Commit additions ChainedHeader newTip = this.chain.GetBlock(blocks.Last().GetHash()); - this.SaveTipHashAndHeight(dbreezeTransaction, newTip); - dbreezeTransaction.Commit(); - - // Update the lookups last to ensure store integrity. - this.UpdateLookups(tracker); + this.SaveTipHashAndHeight(xdbTransaction, newTip); + xdbTransaction.Commit(); } catch (Exception err) { // Restore expected store state in case the calling code retries / continues using the store. this.TipHashAndHeight = prevTip; - this.RollbackAndThrowTransactionError(dbreezeTransaction, err, "PUT_ERROR"); + this.RollbackAndThrowTransactionError(xdbTransaction, err, "PUT_ERROR"); } } @@ -768,8 +675,6 @@ private void Put(List blocks) /// Returns true if a rewind was performed and false otherwise. private bool RewindIfRequired() { - this.logger.LogTrace("()"); - HashHeightPair tipToChase = this.TipToChase(); if (tipToChase.Hash == this.TipHashAndHeight.HashBlock) @@ -779,9 +684,13 @@ private bool RewindIfRequired() return false; } + this.logger.LogTrace("Rewinding."); + // We are dependent on the wallet manager having dealt with any fork by now. if (this.chain.GetBlock(tipToChase.Hash) == null) { + this.logger.LogTrace("The wallet tip is not found in the chain. Rewinding on behalf of wallet."); + ICollection locators = this.federationWalletManager.GetWallet().BlockLocator; var blockLocator = new BlockLocator { Blocks = locators.ToList() }; ChainedHeader fork = this.chain.FindFork(blockLocator); @@ -793,6 +702,8 @@ private bool RewindIfRequired() if (this.TipHashAndHeight != null && (this.TipHashAndHeight.Height > tipToChase.Height || this.chain.GetBlock(this.TipHashAndHeight.HashBlock)?.Height != this.TipHashAndHeight.Height)) { + this.logger.LogTrace("The chain does not contain our tip."); + // We are ahead of the current chain or on the wrong chain. ChainedHeader fork = this.chain.FindFork(this.TipHashAndHeight.GetLocator()) ?? this.chain.GetBlock(0); @@ -800,25 +711,23 @@ private bool RewindIfRequired() while (fork.Height > tipToChase.Height) fork = fork.Previous; - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) - { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - dbreezeTransaction.ValuesLazyLoadingIsOn = false; + this.logger.LogTrace("Fork height determined to be {0}", fork.Height); + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + { ChainedHeader prevTip = this.TipHashAndHeight; try { - StatusChangeTracker tracker = this.OnDeleteBlocks(dbreezeTransaction, fork.Height); - this.SaveTipHashAndHeight(dbreezeTransaction, fork); - dbreezeTransaction.Commit(); - this.UndoLookups(tracker); + this.OnDeleteBlocks(xdbTransaction, fork.Height); + this.SaveTipHashAndHeight(xdbTransaction, fork); + xdbTransaction.Commit(); } catch (Exception err) { // Restore expected store state in case the calling code retries / continues using the store. this.TipHashAndHeight = prevTip; - this.RollbackAndThrowTransactionError(dbreezeTransaction, err, "REWIND_ERROR"); + this.RollbackAndThrowTransactionError(xdbTransaction, err, "REWIND_ERROR"); } } @@ -838,7 +747,7 @@ private bool Synchronize() { lock (this.lockObj) { - this.logger.LogTrace("()"); + this.logger.LogTrace("Synchronizing."); HashHeightPair tipToChase = this.TipToChase(); if (tipToChase.Hash == this.TipHashAndHeight.HashBlock) @@ -860,13 +769,11 @@ private bool Synchronize() if (this.SynchronizeBatch()) { - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { - dbreezeTransaction.SynchronizeTables(transferTableName, commonTableName); - - this.SaveTipHashAndHeight(dbreezeTransaction, this.TipHashAndHeight); + this.SaveTipHashAndHeight(xdbTransaction, this.TipHashAndHeight); - dbreezeTransaction.Commit(); + xdbTransaction.Commit(); } this.logger.LogTrace("(-):true"); @@ -883,8 +790,6 @@ private bool Synchronize() /// Returns true if we match the chain tip and false if we are behind the tip. private bool SynchronizeBatch() { - this.logger.LogTrace("()"); - // Get a batch of blocks. var blockHashes = new List(); int batchSize = 0; @@ -904,11 +809,15 @@ private bool SynchronizeBatch() break; } + this.logger.LogTrace("Attempting to synchronize a batch of {0} blocks.", batchSize); + List blocks = this.blockRepository.GetBlocksAsync(blockHashes).GetAwaiter().GetResult(); int availableBlocks = blocks.FindIndex(b => (b == null)); if (availableBlocks < 0) availableBlocks = blocks.Count; + this.logger.LogTrace("Available blocks are {0}", availableBlocks); + if (availableBlocks > 0) { Block lastBlock = blocks[availableBlocks - 1]; @@ -922,112 +831,25 @@ private bool SynchronizeBatch() return done; } - /// Loads the tip and hash height. - /// The DBreeze transaction context to use. - /// The hash and height pair. - private ChainedHeader LoadTipHashAndHeight(DBreeze.Transactions.Transaction dbreezeTransaction) - { - var blockLocator = new BlockLocator(); - try - { - Row row = dbreezeTransaction.Select(commonTableName, RepositoryTipKey); - Guard.Assert(row.Exists); - blockLocator.FromBytes(row.Value); - } - catch (Exception) - { - blockLocator.Blocks = new List { this.network.GenesisHash }; - } - - this.TipHashAndHeight = this.chain.GetBlock(blockLocator.Blocks[0]) ?? this.chain.FindFork(blockLocator); - return this.TipHashAndHeight; - } - - /// Saves the tip and hash height. - /// The DBreeze transaction context to use. - /// The new tip to persist. - private void SaveTipHashAndHeight(DBreeze.Transactions.Transaction dbreezeTransaction, ChainedHeader newTip) - { - BlockLocator locator = this.chain.Tip.GetLocator(); - this.TipHashAndHeight = newTip; - dbreezeTransaction.Insert(commonTableName, RepositoryTipKey, locator.ToBytes()); - } - - /// Loads the counter-chain next mature block height. - /// The DBreeze transaction context to use. - /// The hash and height pair. - private int LoadNextMatureHeight(DBreeze.Transactions.Transaction dbreezeTransaction) - { - Row row = dbreezeTransaction.Select(commonTableName, NextMatureTipKey); - if (row.Exists) - this.NextMatureDepositHeight = row.Value; - - return this.NextMatureDepositHeight; - } - - /// Saves the counter-chain next mature block height. - /// The DBreeze transaction context to use. - /// The next mature block height on the counter-chain. - private void SaveNextMatureHeight(DBreeze.Transactions.Transaction dbreezeTransaction, int newTip) - { - this.NextMatureDepositHeight = newTip; - dbreezeTransaction.Insert(commonTableName, NextMatureTipKey, this.NextMatureDepositHeight); - } - /// public Task GetAsync(uint256[] depositIds) { return Task.Run(() => { - this.logger.LogTrace("()"); - this.Synchronize(); ICrossChainTransfer[] res = this.ValidateCrossChainTransfers(this.Get(depositIds)); - this.logger.LogTrace("(-)"); return res; }); } - private ICrossChainTransfer[] Get(uint256[] depositId) - { - using (DBreeze.Transactions.Transaction dbreezeTransaction = this.DBreeze.GetTransaction()) - { - dbreezeTransaction.ValuesLazyLoadingIsOn = false; - - return Get(dbreezeTransaction, depositId); - } - } - - private CrossChainTransfer[] Get(DBreeze.Transactions.Transaction transaction, uint256[] depositId) + private ICrossChainTransfer[] Get(uint256[] depositIds) { - Guard.NotNull(depositId, nameof(depositId)); - - // To boost performance we will access the deposits sorted by deposit id. - var depositDict = new Dictionary(); - for (int i = 0; i < depositId.Length; i++) - depositDict[depositId[i]] = i; - - var byteListComparer = new ByteListComparer(); - List> depositList = depositDict.ToList(); - depositList.Sort((pair1, pair2) => byteListComparer.Compare(pair1.Key.ToBytes(), pair2.Key.ToBytes())); - - var res = new CrossChainTransfer[depositId.Length]; - - foreach (KeyValuePair kv in depositList) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.Read)) { - Row transferRow = transaction.Select(transferTableName, kv.Key.ToBytes()); - - if (transferRow.Exists) - { - var crossChainTransfer = new CrossChainTransfer(); - crossChainTransfer.FromBytes(transferRow.Value, this.network.Consensus.ConsensusFactory); - res[kv.Value] = crossChainTransfer; - } + return this.Get(xdbTransaction, depositIds); } - - return res; } private OutPoint EarliestOutput(Transaction transaction) @@ -1043,8 +865,6 @@ public Task> GetTransactionsByStatusAsync(Cross { lock (this.lockObj) { - this.logger.LogTrace("()"); - this.Synchronize(); uint256[] partialTransferHashes = this.depositsIdsByStatus[status].ToArray(); @@ -1056,80 +876,37 @@ public Task> GetTransactionsByStatusAsync(Cross partialTransfers = partialTransfers.Where(t => t.Status == status).ToArray(); } - this.logger.LogTrace("(-)"); + Dictionary res; + if (sort) { - return partialTransfers + res = partialTransfers .Where(t => t.PartialTransaction != null) .OrderBy(t => EarliestOutput(t.PartialTransaction), Comparer.Create((x, y) => this.federationWalletManager.CompareOutpoints(x, y))) .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + + this.logger.LogTrace("Returning {0} sorted results.", res.Count); + } + else + { + res = partialTransfers + .Where(t => t.PartialTransaction != null) + .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + + this.logger.LogTrace("Returning {0} results.", res.Count); } - return partialTransfers - .Where(t => t.PartialTransaction != null) - .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + return res; } }); } - /// Persist the cross-chain transfer information into the database. - /// The DBreeze transaction context to use. - /// Cross-chain transfer information to be inserted. - private void PutTransfer(DBreeze.Transactions.Transaction dbreezeTransaction, ICrossChainTransfer crossChainTransfer) - { - Guard.NotNull(crossChainTransfer, nameof(crossChainTransfer)); - - this.logger.LogTrace("()"); - - dbreezeTransaction.Insert(transferTableName, crossChainTransfer.DepositTransactionId.ToBytes(), crossChainTransfer); - - this.logger.LogTrace("(-)"); - } - - /// Persist multiple cross-chain transfer information into the database. - /// The DBreeze transaction context to use. - /// Cross-chain transfers to be inserted. - private void PutTransfers(DBreeze.Transactions.Transaction dbreezeTransaction, ICrossChainTransfer[] crossChainTransfers) - { - Guard.NotNull(crossChainTransfers, nameof(crossChainTransfers)); - - this.logger.LogTrace("()"); - - // Optimal ordering for DB consumption. - var byteListComparer = new ByteListComparer(); - List orderedTransfers = crossChainTransfers.ToList(); - orderedTransfers.Sort((pair1, pair2) => byteListComparer.Compare(pair1.DepositTransactionId.ToBytes(), pair2.DepositTransactionId.ToBytes())); - - // Write each transfer in order. - foreach (ICrossChainTransfer transfer in orderedTransfers) - { - dbreezeTransaction.Insert(transferTableName, transfer.DepositTransactionId.ToBytes(), transfer); - } - - this.logger.LogTrace("(-)"); - } - - /// Deletes the cross-chain transfer information from the database - /// The DBreeze transaction context to use. - /// Cross-chain transfer information to be deleted. - private void DeleteTransfer(DBreeze.Transactions.Transaction dbreezeTransaction, ICrossChainTransfer crossChainTransfer) - { - Guard.NotNull(crossChainTransfer, nameof(crossChainTransfer)); - - this.logger.LogTrace("()"); - - dbreezeTransaction.RemoveKey(transferTableName, crossChainTransfer.DepositTransactionId.ToBytes()); - - this.logger.LogTrace("(-)"); - } - /// - /// Forgets transfer information for the blocks being removed and returns information for updating the transient lookups. + /// Forgets transfer information for the blocks being removed. /// - /// The DBreeze transaction context to use. + /// The cross-chain db transaction context to use. /// The last block to retain. - /// A tracker with all the cross chain transfers that were affected. - private StatusChangeTracker OnDeleteBlocks(DBreeze.Transactions.Transaction dbreezeTransaction, int lastBlockHeight) + private void OnDeleteBlocks(CrossChainDBTransaction xdbTransaction, int lastBlockHeight) { // Gather all the deposit ids that may have had transactions in the blocks being deleted. var depositIds = new HashSet(); @@ -1141,87 +918,25 @@ private StatusChangeTracker OnDeleteBlocks(DBreeze.Transactions.Transaction dbre } // Find the transfers related to these deposit ids in the database. - var tracker = new StatusChangeTracker(); - CrossChainTransfer[] crossChainTransfers = this.Get(dbreezeTransaction, depositIds.ToArray()); + ICrossChainTransfer[] crossChainTransfers = this.Get(xdbTransaction, depositIds.ToArray()); foreach (CrossChainTransfer transfer in crossChainTransfers) { // Transfers that only exist in the DB due to having been seen in a block should be removed completely. if (transfer.DepositHeight == null) { - // Trigger deletion from the status lookup. - tracker.SetTransferStatus(transfer); - // Delete the transfer completely. - this.DeleteTransfer(dbreezeTransaction, transfer); + xdbTransaction.DeleteTransfer(transfer); } else { // Transaction is no longer seen. - tracker.SetTransferStatus(transfer, CrossChainTransferStatus.FullySigned); + transfer.SetStatus(CrossChainTransferStatus.FullySigned); // Write the transfer status to the database. - this.PutTransfer(dbreezeTransaction, transfer); + xdbTransaction.PutTransfer(transfer); } } - - return tracker; - } - - /// Updates the status lookup based on a transfer and its previous status. - /// The cross-chain transfer that was update. - /// The old status. - private void TransferStatusUpdated(ICrossChainTransfer transfer, CrossChainTransferStatus? oldStatus) - { - if (oldStatus != null) - { - this.depositsIdsByStatus[(CrossChainTransferStatus)oldStatus].Remove(transfer.DepositTransactionId); - } - - this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); - } - - /// Update the transient lookups after changes have been committed to the store. - /// Information about how to update the lookups. - private void UpdateLookups(StatusChangeTracker tracker) - { - foreach (uint256 hash in tracker.UniqueBlockHashes()) - { - this.depositIdsByBlockHash[hash] = new HashSet(); - } - - foreach (KeyValuePair kv in tracker) - { - this.TransferStatusUpdated(kv.Key, kv.Value); - - if (kv.Key.BlockHash != null && kv.Key.BlockHeight != null) - { - if (!this.depositIdsByBlockHash[kv.Key.BlockHash].Contains(kv.Key.DepositTransactionId)) - this.depositIdsByBlockHash[kv.Key.BlockHash].Add(kv.Key.DepositTransactionId); - this.blockHeightsByBlockHash[kv.Key.BlockHash] = (int)kv.Key.BlockHeight; - } - } - } - - /// Undoes the transient lookups after block removals have been committed to the store. - /// Information about how to undo the lookups. - private void UndoLookups(StatusChangeTracker tracker) - { - foreach (KeyValuePair kv in tracker) - { - if (kv.Value == null) - { - this.depositsIdsByStatus[kv.Key.Status].Remove(kv.Key.DepositTransactionId); - } - - this.TransferStatusUpdated(kv.Key, kv.Value); - } - - foreach (uint256 hash in tracker.UniqueBlockHashes()) - { - this.depositIdsByBlockHash.Remove(hash); - this.blockHeightsByBlockHash.Remove(hash); - } } public bool ValidateTransaction(Transaction transaction, bool checkSignature = false) @@ -1242,11 +957,11 @@ public Dictionary GetCrossChainTransferStatusCoun } /// - public void Dispose() + public override void Dispose() { this.SaveCurrentTipAsync().GetAwaiter().GetResult(); this.cancellation.Cancel(); - this.DBreeze.Dispose(); + base.Dispose(); } } } \ No newline at end of file From a2dc8b311383d4251d91008e6def16f1a1c74d99 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 3 Jan 2019 20:08:27 +1100 Subject: [PATCH 05/15] Refactor --- .../Interfaces/IChangeTracker.cs | 12 ++ .../Interfaces/ICrossChainDB.cs | 12 +- .../Interfaces/IUpdateLookups.cs | 7 +- .../TargetChain/CrossChainDB.cs | 37 +++-- .../TargetChain/CrossChainDBTransaction.cs | 127 ++++++++++++------ .../TargetChain/CrossChainTransferStore.cs | 63 +++++---- .../TargetChain/StatusChangeTracker.cs | 28 +++- 7 files changed, 191 insertions(+), 95 deletions(-) create mode 100644 src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs new file mode 100644 index 00000000..fe937ecc --- /dev/null +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs @@ -0,0 +1,12 @@ +using NBitcoin; + +namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces +{ + public interface IChangeTracker + { + void RecordValue(IBitcoinSerializable obj, object value); + void RecordDbValue(IBitcoinSerializable obj); + object GetDbValue(IBitcoinSerializable obj); + void SetDbValue(IBitcoinSerializable obj); + } +} diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs index 63df72a2..39ed3243 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using NBitcoin; using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces @@ -16,9 +18,15 @@ public interface ICrossChainDB : IDisposable /// The object. CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read); + /// + /// Creates trackers for recording information on how to update the lookups. + /// + /// Trackers for recording information on how to update the lookups. + Dictionary CreateTrackers(); + /// Updates the internal lookups based on the changes recorded in the tracker object. - /// Information about how to update the lookups. + /// Trackers recording information about how to update the lookups. /// This method should is only intended be called by the class. - void UpdateLookups(StatusChangeTracker tracker); + void UpdateLookups(Dictionary trackers); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs index 02b8f5a4..6fd54e31 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs @@ -1,9 +1,12 @@ -using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; +using System; +using System.Collections.Generic; +using NBitcoin; namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { public interface ICrossChainLookups { - void UpdateLookups(StatusChangeTracker tracker); + Dictionary CreateTrackers(); + void UpdateLookups(Dictionary trackers); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs index 2d441985..057308cf 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs @@ -141,7 +141,7 @@ public void Initialize() /// public CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read) { - CrossChainDBTransaction xdbTransaction = CrossChainDBTransaction.GetTransaction(this.DBreeze, this.DBreezeSerializer, (ICrossChainLookups)this, mode); + CrossChainDBTransaction xdbTransaction = new CrossChainDBTransaction(this.DBreeze, this.DBreezeSerializer, (ICrossChainLookups)this, mode); this.logger.LogTrace("Transaction '{0}' created for {1}.", xdbTransaction, mode); @@ -309,22 +309,21 @@ protected void SaveNextMatureHeight(CrossChainDBTransaction xdbTransaction, int } } - /// Updates the status lookup based on a transfer and its previous status. - /// The cross-chain transfer that was update. - /// The old status. - private void TransferStatusUpdated(ICrossChainTransfer transfer, CrossChainTransferStatus? oldStatus) + /// + public Dictionary CreateTrackers() { - if (oldStatus != null) - { - this.depositsIdsByStatus[(CrossChainTransferStatus)oldStatus].Remove(transfer.DepositTransactionId); - } + var trackers = new Dictionary(); - this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); + trackers[typeof(ICrossChainTransfer)] = new StatusChangeTracker(); + + return trackers; } /// - public void UpdateLookups(StatusChangeTracker tracker) + public void UpdateLookups(Dictionary trackers) { + StatusChangeTracker tracker = (StatusChangeTracker)trackers[typeof(ICrossChainTransfer)]; + foreach (uint256 hash in tracker.UniqueBlockHashes()) { if (this.depositIdsByBlockHash.ContainsKey(hash)) continue; @@ -334,7 +333,6 @@ public void UpdateLookups(StatusChangeTracker tracker) foreach (KeyValuePair kv in tracker) { ICrossChainTransfer transfer = kv.Key; - CrossChainTransferStatus? status = kv.Value; if (transfer.DepositHeight == null && transfer.DbStatus != null /* Not new */) { @@ -344,7 +342,7 @@ public void UpdateLookups(StatusChangeTracker tracker) } else { - this.TransferStatusUpdated(transfer, status); + this.TransferStatusUpdated(transfer, tracker[transfer]); if (transfer.BlockHash != null && transfer.BlockHeight != null) { @@ -358,6 +356,19 @@ public void UpdateLookups(StatusChangeTracker tracker) this.logger.LogTrace("Lookups updated from tracker containing {0} items.", tracker.Count); } + /// Updates the status lookup based on a transfer and its previous status. + /// The cross-chain transfer that was updated. + /// The old status. + private void TransferStatusUpdated(ICrossChainTransfer transfer, CrossChainTransferStatus? oldStatus) + { + if (oldStatus != null) + { + this.depositsIdsByStatus[(CrossChainTransferStatus)oldStatus].Remove(transfer.DepositTransactionId); + } + + this.depositsIdsByStatus[transfer.Status].Add(transfer.DepositTransactionId); + } + public virtual void Dispose() { this.DBreeze.Dispose(); diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index fcbb2ce3..c8307c3d 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using DBreeze; using DBreeze.DataTypes; using NBitcoin; using Stratis.Bitcoin.Utilities; @@ -8,106 +7,152 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain { + /// Supported DBreeze transaction modes. public enum CrossChainTransactionMode { Read, ReadWrite } + /// + /// The purpose of this class is to restrict the operations that can be performed on the underlying + /// database - i.e. it provides a "higher level" layer to the underlying DBreeze transaction. + /// As such it provides a guarantees that any transient lookups will be kept in step with changes + /// to the database. It also handles all required serialization here in one place. + /// public class CrossChainDBTransaction : IDisposable { + /// The serializer to use for this transaction. private readonly DBreezeSerializer dBreezeSerializer; + + /// The underlying DBreeze transaction. private DBreeze.Transactions.Transaction transaction; + + /// Interface providing control over the updating of transient lookups. private readonly ICrossChainLookups crossChainLookups; + + /// The mode of the transaction. private readonly CrossChainTransactionMode mode; - private StatusChangeTracker tracker; - private CrossChainDBTransaction(DBreeze.Transactions.Transaction transaction, DBreezeSerializer dbreezeSerializer, ICrossChainLookups updateLookups, CrossChainTransactionMode mode) + /// Tracking changes allows updating of transient lookups after a successful commit operation. + private Dictionary trackers; + + /// + /// Constructs a transaction object that acts as a wrapper around the database tables. + /// + /// The DBreeze database engine. + /// The DBreeze serializer to use. + /// Interface providing methods to orchestrate the updating of transient lookups. + /// The mode in which to interact with the database. + public CrossChainDBTransaction( + DBreeze.DBreezeEngine dbreeze, + DBreezeSerializer dbreezeSerializer, + ICrossChainLookups updateLookups, + CrossChainTransactionMode mode) { - this.transaction = transaction; + this.transaction = dbreeze.GetTransaction(); this.dBreezeSerializer = dbreezeSerializer; this.crossChainLookups = updateLookups; this.mode = mode; - transaction.ValuesLazyLoadingIsOn = false; + this.transaction.ValuesLazyLoadingIsOn = false; if (mode == CrossChainTransactionMode.ReadWrite) { - transaction.SynchronizeTables(CrossChainDB.TransferTableName, CrossChainDB.CommonTableName); + this.transaction.SynchronizeTables(CrossChainDB.TransferTableName, CrossChainDB.CommonTableName); } - this.tracker = new StatusChangeTracker(); + this.trackers = updateLookups.CreateTrackers(); } - public static CrossChainDBTransaction GetTransaction(DBreezeEngine dBreezeEngine, DBreezeSerializer dbreezeSerializer, ICrossChainLookups updateLookups, CrossChainTransactionMode mode) + private void Insert(string tableName, TKey key, TValue value) where TValue : IBitcoinSerializable { - return new CrossChainDBTransaction(dBreezeEngine.GetTransaction(eTransactionTablesLockTypes.EXCLUSIVE), dbreezeSerializer, updateLookups, mode); + Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + + byte[] keyBytes = this.dBreezeSerializer.Serialize(key); + byte[] valueBytes = this.dBreezeSerializer.Serialize(value); + this.transaction.Insert(tableName, keyBytes, valueBytes); + if (this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) + tracker.RecordDbValue(value); } - public ICrossChainTransfer GetTransfer(uint256 depositId) + private bool Select(string tableName, TKey key, out TValue value) where TValue : IBitcoinSerializable { - Row transferRow = this.transaction.Select(CrossChainDB.TransferTableName, depositId.ToBytes()); + byte[] keyBytes = this.dBreezeSerializer.Serialize(key); + Row row = this.transaction.Select(tableName, keyBytes); + value = row.Exists ? this.dBreezeSerializer.Deserialize(row.Value) : default(TValue); + if (this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) + tracker.SetDbValue(value); + return row.Exists; + } - if (transferRow.Exists) - { - // Workaround for shortcoming in DBreeze serialization. - var crossChainTransfer = new CrossChainTransfer(); - crossChainTransfer.FromBytes(transferRow.Value, this.dBreezeSerializer.Network.Consensus.ConsensusFactory); - crossChainTransfer.RecordDbStatus(); + private IEnumerable SelectForward(string tableName) where TValue : IBitcoinSerializable + { + if (!this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) + tracker = null; - return crossChainTransfer; + foreach (Row row in this.transaction.SelectForward(tableName)) + { + TValue value = this.dBreezeSerializer.Deserialize(row.Value); + if (tracker != null) + tracker.SetDbValue(value); + yield return value; } + } - return null; + private void RemoveKey(string tableName, TKey key, TValue oldValue) where TValue : IBitcoinSerializable + { + Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + + byte[] keyBytes = this.dBreezeSerializer.Serialize(key); + this.transaction.RemoveKey(tableName, keyBytes); + if (!this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) + tracker.RecordDbValue(oldValue); } - public IEnumerable EnumerateTransfers() + public ICrossChainTransfer GetTransfer(uint256 depositId) { - foreach (Row transferRow in this.transaction.SelectForward(CrossChainDB.TransferTableName)) - { - // Workaround for shortcoming in DBreeze serialization. - var crossChainTransfer = new CrossChainTransfer(); - crossChainTransfer.FromBytes(transferRow.Value, this.dBreezeSerializer.Network.Consensus.ConsensusFactory); - crossChainTransfer.RecordDbStatus(); + if (!Select(CrossChainDB.TransferTableName, depositId, out ICrossChainTransfer crossChainTransfer)) + return null; + + return crossChainTransfer; + } + public IEnumerable EnumerateTransfers() + { + foreach (ICrossChainTransfer crossChainTransfer in SelectForward(CrossChainDB.TransferTableName)) yield return crossChainTransfer; - } } public void PutTransfer(ICrossChainTransfer transfer) { - Guard.Assert(this.mode != CrossChainTransactionMode.Read); - - // Record the old status - this.tracker[transfer] = transfer.DbStatus; + Guard.NotNull(transfer, nameof(transfer)); // Write the transfer. - this.transaction.Insert(CrossChainDB.TransferTableName, transfer.DepositTransactionId.ToBytes(), transfer); + Insert(CrossChainDB.TransferTableName, transfer.DepositTransactionId, transfer); } public void DeleteTransfer(ICrossChainTransfer transfer) { Guard.NotNull(transfer, nameof(transfer)); - // Only transfers that exist in the db purely due to being seen in a block will be removed. + // Only transfers that exist in the db solely due to being seen in a block will be removed. Guard.Assert(transfer.DepositHeight == null); - this.tracker[transfer] = transfer.DbStatus; - - this.transaction.RemoveKey(CrossChainDB.TransferTableName, transfer.DepositTransactionId.ToBytes()); + RemoveKey(CrossChainDB.TransferTableName, transfer.DepositTransactionId, transfer); } public void Commit() { - Guard.Assert(this.mode != CrossChainTransactionMode.Read); + Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); this.transaction.Commit(); - this.crossChainLookups.UpdateLookups(this.tracker); + this.crossChainLookups.UpdateLookups(this.trackers); } public void Rollback() { - Guard.Assert(this.mode != CrossChainTransactionMode.Read); + Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); this.transaction.Rollback(); } @@ -127,7 +172,6 @@ public BlockLocator LoadTipHashAndHeight() } return blockLocator; - } public void SaveTipHashAndHeight(BlockLocator blockLocator) @@ -155,7 +199,8 @@ public void SaveNextMatureHeight(int newTip) /// A concatenation of the creation time and thread id. public override string ToString() { - return string.Format("{0}({1})", this.transaction.CreatedUdt.GetHashCode(), this.transaction.ManagedThreadId); + DateTime createdDT = new DateTime(this.transaction.CreatedUdt); + return string.Format("{0}:{1}", createdDT, this.transaction.ManagedThreadId); } public void Dispose() diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index 1bf4ad92..dab35363 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -89,7 +89,7 @@ public void Start() this.logger.LogTrace("Adding any missing but seen transactions to wallet."); FederationWallet wallet = this.federationWalletManager.GetWallet(); - ICrossChainTransfer[] transfers = Get(this.depositsIdsByStatus[CrossChainTransferStatus.SeenInBlock].ToArray()); + ICrossChainTransfer[] transfers = this.GetTransfersByStatus(new[] { CrossChainTransferStatus.SeenInBlock }, true, false).ToArray(); foreach (ICrossChainTransfer transfer in transfers) { (Transaction tran, TransactionData tranData, _) = this.federationWalletManager.FindWithdrawalTransactions(transfer.DepositTransactionId).FirstOrDefault(); @@ -849,46 +849,43 @@ private OutPoint EarliestOutput(Transaction transaction) return transaction.Inputs.Select(i => i.PrevOut).OrderByDescending(t => t, comparer).FirstOrDefault(); } - /// - public Task> GetTransactionsByStatusAsync(CrossChainTransferStatus status, bool sort = false) + private ICrossChainTransfer[] GetTransfersByStatus(CrossChainTransferStatus[] statuses, bool sort = false, bool validate = true) { - return Task.Run(() => + lock (this.lockObj) { - lock (this.lockObj) - { - this.Synchronize(); - - uint256[] partialTransferHashes = this.depositsIdsByStatus[status].ToArray(); - ICrossChainTransfer[] partialTransfers = this.Get(partialTransferHashes).ToArray(); + this.Synchronize(); - if (status == CrossChainTransferStatus.Partial || status == CrossChainTransferStatus.FullySigned) - { - this.ValidateCrossChainTransfers(partialTransfers); - partialTransfers = partialTransfers.Where(t => t.Status == status).ToArray(); - } + var depositIds = new HashSet(); + foreach (CrossChainTransferStatus status in statuses) + depositIds.UnionWith(this.depositsIdsByStatus[status]); - Dictionary res; + uint256[] partialTransferHashes = depositIds.ToArray(); + ICrossChainTransfer[] partialTransfers = this.Get(partialTransferHashes).Where(t => t != null).ToArray(); - if (sort) - { - res = partialTransfers - .Where(t => t.PartialTransaction != null) - .OrderBy(t => EarliestOutput(t.PartialTransaction), Comparer.Create((x, y) => this.federationWalletManager.CompareOutpoints(x, y))) - .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + if (validate) + { + this.ValidateCrossChainTransfers(partialTransfers); + partialTransfers = partialTransfers.Where(t => statuses.Contains(t.Status)).ToArray(); + } - this.logger.LogTrace("Returning {0} sorted results.", res.Count); - } - else - { - res = partialTransfers - .Where(t => t.PartialTransaction != null) - .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + if (!sort) + { + return partialTransfers; + } - this.logger.LogTrace("Returning {0} results.", res.Count); - } + return partialTransfers.OrderBy(t => this.EarliestOutput(t.PartialTransaction), Comparer.Create((x, y) => + this.federationWalletManager.CompareOutpoints(x, y))).ToArray(); + } + } - return res; - } + /// + public Task> GetTransactionsByStatusAsync(CrossChainTransferStatus status, bool sort = false) + { + return Task.Run(() => + { + return this.GetTransfersByStatus(new[] { status }, sort) + .Where(t => t.PartialTransaction != null) + .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); }); } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs index ff55ff45..d65655ed 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs @@ -5,8 +5,28 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain { - public class StatusChangeTracker : Dictionary + public class StatusChangeTracker : Dictionary, IChangeTracker { + public void RecordValue(IBitcoinSerializable transfer, object status) + { + this[(ICrossChainTransfer)transfer] = (CrossChainTransferStatus)status; + } + + public void RecordDbValue(IBitcoinSerializable transfer) + { + RecordValue(transfer, GetDbValue(transfer)); + } + + public object GetDbValue(IBitcoinSerializable transfer) + { + return ((ICrossChainTransfer)transfer).DbStatus; + } + + public void SetDbValue(IBitcoinSerializable transfer) + { + ((ICrossChainTransfer)transfer).RecordDbStatus(); + } + /// /// Records changes to transfers for the purpose of synchronizing the transient lookups after the DB commit. /// @@ -23,13 +43,13 @@ public void SetTransferStatus(ICrossChainTransfer transfer, CrossChainTransferSt if (status != null) { // If setting the status then record the previous status. - this[transfer] = transfer.Status; + RecordValue(transfer, transfer.Status); transfer.SetStatus((CrossChainTransferStatus)status, blockHash, blockHeight); } else { - // If not setting the status then assume there is no previous status. - this[transfer] = null; + // This is a new object and there is no previous status. + RecordValue(transfer, null); } } From 8a3a34a1850ad982927f5e5e00397cc9e1c7b2c0 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 3 Jan 2019 20:56:12 +1100 Subject: [PATCH 06/15] Merge with upstream changes --- .../TargetChain/CrossChainTransferStore.cs | 89 +++++++++++-------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index dab35363..32917938 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -44,7 +44,6 @@ public CrossChainTransferStore(Network network, DataFolder dataFolder, Concurren Guard.NotNull(dataFolder, nameof(dataFolder)); Guard.NotNull(chain, nameof(chain)); Guard.NotNull(settings, nameof(settings)); - Guard.NotNull(dateTimeProvider, nameof(dateTimeProvider)); Guard.NotNull(loggerFactory, nameof(loggerFactory)); Guard.NotNull(withdrawalExtractor, nameof(withdrawalExtractor)); Guard.NotNull(fullNode, nameof(fullNode)); @@ -54,7 +53,6 @@ public CrossChainTransferStore(Network network, DataFolder dataFolder, Concurren this.network = network; this.chain = chain; - this.dateTimeProvider = dateTimeProvider; this.blockRepository = blockRepository; this.federationWalletManager = federationWalletManager; this.federationWalletTransactionHandler = federationWalletTransactionHandler; @@ -121,6 +119,7 @@ private HashHeightPair TipToChase() if (wallet?.LastBlockSyncedHeight == null) { + this.logger.LogTrace("(-)[GENESIS]"); return new HashHeightPair(this.network.GenesisHash, 0); } @@ -136,8 +135,6 @@ private HashHeightPair TipToChase() /// Returns the list of transfers, possible with updated statuses. private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] crossChainTransfers = null) { - FederationWallet wallet = this.federationWalletManager.GetWallet(); - if (crossChainTransfers == null) { crossChainTransfers = Get( @@ -157,7 +154,7 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] continue; List<(Transaction, TransactionData, IWithdrawal)> walletData = this.federationWalletManager.FindWithdrawalTransactions(partialTransfer.DepositTransactionId); - if (walletData.Count == 1 && ValidateTransaction(walletData[0].Item1)) + if (walletData.Count == 1 && this.ValidateTransaction(walletData[0].Item1)) { Transaction walletTran = walletData[0].Item1; if (walletTran.GetHash() == partialTransfer.PartialTransaction.GetHash()) @@ -172,7 +169,7 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] if (walletData[0].Item2.BlockHeight != null) tracker.SetTransferStatus(partialTransfer, CrossChainTransferStatus.SeenInBlock, walletData[0].Item2.BlockHash, (int)walletData[0].Item2.BlockHeight); - else if (ValidateTransaction(walletTran, true)) + else if (this.ValidateTransaction(walletTran, true)) tracker.SetTransferStatus(partialTransfer, CrossChainTransferStatus.FullySigned); else tracker.SetTransferStatus(partialTransfer, CrossChainTransferStatus.Partial); @@ -183,7 +180,7 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] // The chain may have been rewound so that this transaction or its UTXO's have been lost. // Rewind our recorded chain A tip to ensure the transaction is re-built once UTXO's become available. - if (partialTransfer.DepositHeight != null && partialTransfer.DepositHeight < newChainATip) + if (partialTransfer.DepositHeight < newChainATip) { newChainATip = (int)partialTransfer.DepositHeight; @@ -196,7 +193,10 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] // Exit if nothing to do. if (tracker.Count == 0) + { + this.logger.LogTrace("(-)[NO_CHANGES_IN_TRACKER]"); return crossChainTransfers; + } using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) { @@ -250,13 +250,14 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] } } - private Transaction BuildDeterministicTransaction(uint256 depositId, Recipient recipient) + private Transaction BuildDeterministicTransaction(uint256 depositId, uint blockTime, Recipient recipient) { try { // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; + bool sign = (walletPassword ?? "") != ""; var multiSigContext = new TransactionBuildContext(new[] { recipient }.ToList(), opReturnData: opReturnData.ToBytes()) { OrderCoinsDeterministic = true, @@ -265,24 +266,34 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, Recipient r Shuffle = false, IgnoreVerify = true, WalletPassword = walletPassword, - Sign = (walletPassword ?? "") != "" + Sign = sign }; // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); + if (this.network.Consensus.IsProofOfStake) + { + transaction.Time = blockTime; + + if (sign) + { + transaction = multiSigContext.TransactionBuilder.SignTransaction(transaction); + } + } if (transaction == null) this.logger.LogTrace("Failed to create deterministic transaction."); else - this.logger.LogTrace("Deterministic transaction created."); + this.logger.LogInformation("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer)); return transaction; } catch (Exception error) { - this.logger.LogTrace("Could not create transaction for deposit {0}: {1}", depositId, error.Message); + this.logger.LogError("Could not create transaction for deposit {0}: {1}", depositId, error.Message); } + this.logger.LogTrace("(-)[FAIL]"); return null; } @@ -321,31 +332,28 @@ public Task RecordLatestMatureDepositsAsync(IList a.BlockInfo.BlockHeight) .SkipWhile(m => m.BlockInfo.BlockHeight < this.NextMatureDepositHeight).ToArray(); - if (maturedBlockDeposits.Count == 0 || - maturedBlockDeposits.First().BlockInfo.BlockHeight != this.NextMatureDepositHeight) + if (maturedBlockDeposits.Count == 0 || maturedBlockDeposits.First().BlockInfo.BlockHeight != this.NextMatureDepositHeight) { this.logger.LogTrace("No block found starting at height {0}.", this.NextMatureDepositHeight); - this.logger.LogTrace("(-):[NO_VIABLE_BLOCKS]"); + this.logger.LogTrace("(-)[NO_VIABLE_BLOCKS]:true"); return true; } if (maturedBlockDeposits.Last().BlockInfo.BlockHeight != this.NextMatureDepositHeight + maturedBlockDeposits.Count - 1) { this.logger.LogTrace("Input containing duplicate blocks will be ignored."); - this.logger.LogTrace("(-):[DUPLICATE_BLOCKS]"); + this.logger.LogTrace("(-)[DUPLICATE_BLOCKS]:true"); return true; } this.Synchronize(); - FederationWallet wallet = this.federationWalletManager.GetWallet(); - - for (int j = 0; j < maturedBlockDeposits.Count; j++) + foreach (MaturedBlockDepositsModel maturedDeposit in maturedBlockDeposits) { - if (maturedBlockDeposits[j].BlockInfo.BlockHeight != this.NextMatureDepositHeight) + if (maturedDeposit.BlockInfo.BlockHeight != this.NextMatureDepositHeight) continue; - IReadOnlyList deposits = maturedBlockDeposits[j].Deposits; + IReadOnlyList deposits = maturedDeposit.Deposits; if (deposits.Count == 0) { this.NextMatureDepositHeight++; @@ -354,8 +362,7 @@ public Task RecordLatestMatureDepositsAsync(IList RecordLatestMatureDepositsAsync(IList RecordLatestMatureDepositsAsync(IList RecordLatestMatureDepositsAsync(IList RecordLatestMatureDepositsAsync(IList MergeTransactionSignaturesAsync(uint256 depositId, Tran this.logger.LogTrace("()"); this.Synchronize(); - FederationWallet wallet = this.federationWalletManager.GetWallet(); ICrossChainTransfer transfer = this.ValidateCrossChainTransfers(this.Get(new[] { depositId })).FirstOrDefault(); if (transfer == null) { + this.logger.LogInformation("FAILED ValidateCrossChainTransfers : {0}", depositId); this.logger.LogTrace("(-)[MERGE_NOTFOUND]"); return null; } if (transfer.Status != CrossChainTransferStatus.Partial) { - this.logger.LogTrace("(-)[MERGE_BADSTATUS]"); + this.logger.LogTrace("(-)[MERGE_BAD_STATUS]"); return transfer.PartialTransaction; } @@ -514,6 +524,7 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran if (transfer.PartialTransaction.GetHash() == oldTransaction.GetHash()) { + this.logger.LogInformation("FAILED to combineSignatures : {0}", transfer.DepositTransactionId); this.logger.LogTrace("(-)[MERGE_UNCHANGED]"); return transfer.PartialTransaction; } @@ -527,6 +538,7 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran if (ValidateTransaction(transfer.PartialTransaction, true)) { + this.logger.LogInformation("Deposit: {0} collected enough signatures and is FullySigned", transfer.DepositTransactionId); transfer.SetStatus(CrossChainTransferStatus.FullySigned); } @@ -535,6 +547,8 @@ public Task MergeTransactionSignaturesAsync(uint256 depositId, Tran } catch (Exception err) { + this.logger.LogError("Error: {0} ", err); + // Restore expected store state in case the calling code retries / continues using the store. transfer.SetPartialTransaction(oldTransaction); this.federationWalletManager.ProcessTransaction(oldTransaction); @@ -560,7 +574,7 @@ private void Put(List blocks) this.logger.LogTrace("Putting {0} blocks.", blocks.Count); if (blocks.Count == 0) - this.logger.LogTrace("(-):0"); + this.logger.LogTrace("(-)[NO_BLOCKS]:0"); Dictionary transferLookup; Dictionary allWithdrawals; @@ -582,7 +596,7 @@ private void Put(List blocks) // Exiting here and saving the tip after the sync. this.TipHashAndHeight = this.chain.GetBlock(blocks.Last().GetHash()); - this.logger.LogTrace("(-)[NO_DEPOSITS]"); + this.logger.LogTrace("(-)[NO_DEPOSIT_IDS]"); return; } @@ -671,7 +685,7 @@ private bool RewindIfRequired() if (tipToChase.Hash == this.TipHashAndHeight.HashBlock) { // Indicate that we are synchronized. - this.logger.LogTrace("(-):false"); + this.logger.LogTrace("(-)[SYNCHRONIZED]:false"); return false; } @@ -689,7 +703,7 @@ private bool RewindIfRequired() tipToChase = this.TipToChase(); } - // If the chain does not contain our tip.. + // If the chain does not contain our tip. if (this.TipHashAndHeight != null && (this.TipHashAndHeight.Height > tipToChase.Height || this.chain.GetBlock(this.TipHashAndHeight.HashBlock)?.Height != this.TipHashAndHeight.Height)) { @@ -744,7 +758,7 @@ private bool Synchronize() if (tipToChase.Hash == this.TipHashAndHeight.HashBlock) { // Indicate that we are synchronized. - this.logger.LogTrace("(-):true"); + this.logger.LogTrace("(-)[SYNCHRONIZED]:true"); return true; } @@ -813,7 +827,7 @@ private bool SynchronizeBatch() { Block lastBlock = blocks[availableBlocks - 1]; this.Put(blocks.GetRange(0, availableBlocks)); - this.logger.LogInformation("Synchronized {0} blocks with cross-chain store to advance tip to block {1}", availableBlocks, this.TipHashAndHeight.Height); + this.logger.LogInformation("Synchronized {0} blocks with cross-chain store to advance tip to block {1}", availableBlocks, this.TipHashAndHeight?.Height); } bool done = availableBlocks < synchronizationBatchSize; @@ -874,7 +888,7 @@ private ICrossChainTransfer[] GetTransfersByStatus(CrossChainTransferStatus[] st } return partialTransfers.OrderBy(t => this.EarliestOutput(t.PartialTransaction), Comparer.Create((x, y) => - this.federationWalletManager.CompareOutpoints(x, y))).ToArray(); + this.federationWalletManager.CompareOutpoints(x, y))).ToArray(); } } @@ -883,9 +897,8 @@ public Task> GetTransactionsByStatusAsync(Cross { return Task.Run(() => { - return this.GetTransfersByStatus(new[] { status }, sort) - .Where(t => t.PartialTransaction != null) - .ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); + ICrossChainTransfer[] res = this.GetTransfersByStatus(new[] { status }, sort); + return res.Where(t => t.PartialTransaction != null).ToDictionary(t => t.DepositTransactionId, t => t.PartialTransaction); }); } From 07e587cae3f43a47fa0fafd1c11e27112f7f194e Mon Sep 17 00:00:00 2001 From: quantumagi Date: Thu, 3 Jan 2019 21:07:14 +1100 Subject: [PATCH 07/15] Merge with upstream changes --- .../TargetChain/CrossChainTransferStore.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index 32917938..10030ab6 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -254,6 +254,8 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, uint blockT { try { + this.logger.LogInformation("BuildDeterministicTransaction depositId(opReturnData)={0} recipient.ScriptPubKey={1} recipient.Amount={2}", depositId, recipient.ScriptPubKey, recipient.Amount); + // Build the multisig transaction template. uint256 opReturnData = depositId; string walletPassword = this.federationWalletManager.Secret.WalletPassword; @@ -271,20 +273,24 @@ private Transaction BuildDeterministicTransaction(uint256 depositId, uint blockT // Build the transaction. Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext); - if (this.network.Consensus.IsProofOfStake) + if (transaction == null) { - transaction.Time = blockTime; - - if (sign) + this.logger.LogTrace("Failed to create deterministic transaction."); + } + else + { + if (this.network.Consensus.IsProofOfStake) { - transaction = multiSigContext.TransactionBuilder.SignTransaction(transaction); + transaction.Time = blockTime; + + if (sign) + { + transaction = multiSigContext.TransactionBuilder.SignTransaction(transaction); + } } - } - if (transaction == null) - this.logger.LogTrace("Failed to create deterministic transaction."); - else this.logger.LogInformation("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer)); + } return transaction; } @@ -398,7 +404,7 @@ public Task RecordLatestMatureDepositsAsync(IList MergeTransactionSignaturesAsync(uint256 depositId, Tran this.logger.LogTrace("()"); this.Synchronize(); + this.logger.LogInformation("Get and ValidateCrossChainTransfers : {0}", depositId); ICrossChainTransfer transfer = this.ValidateCrossChainTransfers(this.Get(new[] { depositId })).FirstOrDefault(); if (transfer == null) { - this.logger.LogInformation("FAILED ValidateCrossChainTransfers : {0}", depositId); + this.logger.LogInformation("FAILED Get and ValidateCrossChainTransfers : {0}", depositId); this.logger.LogTrace("(-)[MERGE_NOTFOUND]"); return null; } From c4961e64642709a1e81dbc9741e5ed8d0884f1ce Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 16:10:21 +1100 Subject: [PATCH 08/15] Add comments --- .../Interfaces/IChangeTracker.cs | 18 ++++++++++++++---- .../Interfaces/ICrossChainTransfer.cs | 1 + .../Interfaces/IUpdateLookups.cs | 14 +++++++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs index fe937ecc..bda9f142 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs @@ -2,11 +2,21 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { + /// + /// Tracks changes to made to objects. + /// public interface IChangeTracker { - void RecordValue(IBitcoinSerializable obj, object value); - void RecordDbValue(IBitcoinSerializable obj); - object GetDbValue(IBitcoinSerializable obj); - void SetDbValue(IBitcoinSerializable obj); + /// + /// Records the object (or part of the object) that was originally read from the database. + /// + /// The object to record the original value of. + void RecordOldValue(IBitcoinSerializable obj); + + /// + /// Instructs the object to record its original value (or part thereof). Typically called after reading it from the database. + /// + /// The object being instructed to record its original value. + void SetOldValue(IBitcoinSerializable obj); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs index 0f258a12..c06cd334 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransfer.cs @@ -83,6 +83,7 @@ public interface ICrossChainTransfer : IBitcoinSerializable /// /// Gets the number of sigatures in the first input of the transaction. /// + /// The network associated with the transfer's partial transaction. /// Number of signatures. int GetSignatureCount(Network network); diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs index 6fd54e31..0729e621 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs @@ -1,12 +1,24 @@ using System; using System.Collections.Generic; -using NBitcoin; namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { + /// + /// Provides methods for creating trackers and for updating lookups based on + /// the information contained in the trackers. + /// public interface ICrossChainLookups { + /// + /// Called to create trackers for a transaction. + /// + /// The trackers used by a transaction. Dictionary CreateTrackers(); + + /// + /// Updates the lookups affected by the changes recorded by the trackers. + /// + /// The trackers used by a transaction. void UpdateLookups(Dictionary trackers); } } From 5fc39249ef20d0b202e2d20dba15f6dad44a88c5 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 16:10:33 +1100 Subject: [PATCH 09/15] Refactor / add comments --- .../TargetChain/CrossChainDBTransaction.cs | 56 +++++++++++++------ .../TargetChain/StatusChangeTracker.cs | 36 ++++++------ 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index c8307c3d..c82fa6b5 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -65,49 +65,71 @@ public CrossChainDBTransaction( this.trackers = updateLookups.CreateTrackers(); } - private void Insert(string tableName, TKey key, TValue value) where TValue : IBitcoinSerializable + private void Insert(string tableName, TKey key, TObject obj) where TObject : IBitcoinSerializable { Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); byte[] keyBytes = this.dBreezeSerializer.Serialize(key); - byte[] valueBytes = this.dBreezeSerializer.Serialize(value); - this.transaction.Insert(tableName, keyBytes, valueBytes); - if (this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) - tracker.RecordDbValue(value); + byte[] objBytes = this.dBreezeSerializer.Serialize(obj); + this.transaction.Insert(tableName, keyBytes, objBytes); + + // If this is a tracked class. + if (this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + { + // Record the object and its old value. + tracker.RecordOldValue(obj); + } } - private bool Select(string tableName, TKey key, out TValue value) where TValue : IBitcoinSerializable + private bool Select(string tableName, TKey key, out TObject obj) where TObject : IBitcoinSerializable { byte[] keyBytes = this.dBreezeSerializer.Serialize(key); Row row = this.transaction.Select(tableName, keyBytes); - value = row.Exists ? this.dBreezeSerializer.Deserialize(row.Value) : default(TValue); - if (this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) - tracker.SetDbValue(value); + obj = row.Exists ? this.dBreezeSerializer.Deserialize(row.Value) : default(TObject); + + // If this is a tracked class. + if (this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + { + // Set the old value on the object itself so that we can update the lookups if it is changed. + tracker.SetOldValue(obj); + } + return row.Exists; } - private IEnumerable SelectForward(string tableName) where TValue : IBitcoinSerializable + private IEnumerable SelectForward(string tableName) where TObject : IBitcoinSerializable { - if (!this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) + if (!this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) tracker = null; foreach (Row row in this.transaction.SelectForward(tableName)) { - TValue value = this.dBreezeSerializer.Deserialize(row.Value); + TObject obj = this.dBreezeSerializer.Deserialize(row.Value); + + // If this is a tracked class. if (tracker != null) - tracker.SetDbValue(value); - yield return value; + { + // Set the old value on the object itself so that we can update the lookups if it is changed. + tracker.SetOldValue(obj); + } + + yield return obj; } } - private void RemoveKey(string tableName, TKey key, TValue oldValue) where TValue : IBitcoinSerializable + private void RemoveKey(string tableName, TKey key, TObject obj) where TObject : IBitcoinSerializable { Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); byte[] keyBytes = this.dBreezeSerializer.Serialize(key); this.transaction.RemoveKey(tableName, keyBytes); - if (!this.trackers.TryGetValue(typeof(TValue), out IChangeTracker tracker)) - tracker.RecordDbValue(oldValue); + + // If this is a tracked class. + if (!this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + { + // Record the object and its old value. + tracker.RecordOldValue(obj); + } } public ICrossChainTransfer GetTransfer(uint256 depositId) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs index d65655ed..f008e368 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/StatusChangeTracker.cs @@ -5,30 +5,32 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain { + /// + /// Tracks changed transfers and records their original status. + /// public class StatusChangeTracker : Dictionary, IChangeTracker { - public void RecordValue(IBitcoinSerializable transfer, object status) - { - this[(ICrossChainTransfer)transfer] = (CrossChainTransferStatus)status; - } - - public void RecordDbValue(IBitcoinSerializable transfer) - { - RecordValue(transfer, GetDbValue(transfer)); - } - - public object GetDbValue(IBitcoinSerializable transfer) + /// + /// Records the status that was originally read from the database. + /// + /// The transfer to record the original status of. + public void RecordOldValue(IBitcoinSerializable transfer) { - return ((ICrossChainTransfer)transfer).DbStatus; + this[(ICrossChainTransfer)transfer] = ((ICrossChainTransfer)transfer).DbStatus; } - public void SetDbValue(IBitcoinSerializable transfer) + /// + /// Instructs the transfer to record its (original) status. Typically called after reading it from the database. + /// + /// The transfer that should record its status. + public void SetOldValue(IBitcoinSerializable transfer) { ((ICrossChainTransfer)transfer).RecordDbStatus(); } /// - /// Records changes to transfers for the purpose of synchronizing the transient lookups after the DB commit. + /// This is used by standalone (not created in context) trackers + /// to set the status on a transfer and at the same time note the change in the tracker. /// /// The cross-chain transfer to update. /// The new status. @@ -36,20 +38,20 @@ public void SetDbValue(IBitcoinSerializable transfer) /// The block height of the partialTransaction. /// /// Within the store the earliest status is . In this case null - /// is used to flag a new transfer - a transfer with no earlier status. null is not written to the DB. + /// is used to flag a new transfer within the tracker only. It means that there is no earlier status. /// public void SetTransferStatus(ICrossChainTransfer transfer, CrossChainTransferStatus? status = null, uint256 blockHash = null, int blockHeight = 0) { if (status != null) { // If setting the status then record the previous status. - RecordValue(transfer, transfer.Status); + this[transfer] = transfer.Status; transfer.SetStatus((CrossChainTransferStatus)status, blockHash, blockHeight); } else { // This is a new object and there is no previous status. - RecordValue(transfer, null); + this[transfer] = null; } } From 93d5926f01331b75645182b883587f878e658532 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 16:20:47 +1100 Subject: [PATCH 10/15] Fix comments / interface --- .../Interfaces/IChangeTracker.cs | 2 +- .../Interfaces/ICrossChainDB.cs | 15 +-------------- .../Interfaces/ICrossChainTransferStore.cs | 3 +-- .../Interfaces/IUpdateLookups.cs | 9 +++++---- 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs index bda9f142..8623c98f 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IChangeTracker.cs @@ -3,7 +3,7 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { /// - /// Tracks changes to made to objects. + /// Tracks changes made to objects. /// public interface IChangeTracker { diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs index 39ed3243..3a0fe190 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; -using NBitcoin; using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { - public interface ICrossChainDB : IDisposable + public interface ICrossChainDB : ICrossChainLookups, IDisposable { /// Initializes the cross-chain-transfer store. void Initialize(); @@ -17,16 +15,5 @@ public interface ICrossChainDB : IDisposable /// or /// The object. CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read); - - /// - /// Creates trackers for recording information on how to update the lookups. - /// - /// Trackers for recording information on how to update the lookups. - Dictionary CreateTrackers(); - - /// Updates the internal lookups based on the changes recorded in the tracker object. - /// Trackers recording information about how to update the lookups. - /// This method should is only intended be called by the class. - void UpdateLookups(Dictionary trackers); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs index 1c82fbfc..0fc7f329 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainTransferStore.cs @@ -3,12 +3,11 @@ using System.Threading.Tasks; using NBitcoin; using Stratis.FederatedPeg.Features.FederationGateway.Models; -using Stratis.FederatedPeg.Features.FederationGateway.TargetChain; namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces { /// Interface for interacting with the cross-chain transfer database. - public interface ICrossChainTransferStore : IDisposable, ICrossChainDB, ICrossChainLookups + public interface ICrossChainTransferStore : IDisposable, ICrossChainDB { /// Starts the cross-chain-transfer store. void Start(); diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs index 0729e621..425d415b 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs @@ -10,15 +10,16 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.Interfaces public interface ICrossChainLookups { /// - /// Called to create trackers for a transaction. + /// Creates trackers for recording information on how to update the lookups. /// - /// The trackers used by a transaction. + /// Trackers for recording information on how to update the lookups. Dictionary CreateTrackers(); /// - /// Updates the lookups affected by the changes recorded by the trackers. + /// Updates lookups based on the changes recorded in the tracker object. /// - /// The trackers used by a transaction. + /// Trackers recording information about how to update the lookups. + /// This method is intended be called by the class. void UpdateLookups(Dictionary trackers); } } From 3e7bac983f18a35b04f9dce77a45a8de5fd80486 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 16:54:40 +1100 Subject: [PATCH 11/15] Logging / optimization --- .../TargetChain/CrossChainDB.cs | 2 ++ .../TargetChain/CrossChainTransferStore.cs | 16 +++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs index 057308cf..c4ffe58f 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs @@ -203,6 +203,8 @@ protected void PutTransfers(CrossChainDBTransaction xdbTransaction, ICrossChainT // Write each transfer in order. foreach (ICrossChainTransfer transfer in orderedTransfers) { + this.logger.LogTrace("Registering transfer: {0}.", transfer); + xdbTransaction.PutTransfer(transfer); } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index 10030ab6..97183388 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -204,12 +204,9 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] try { - foreach (KeyValuePair kv in tracker) - { - xdbTransaction.PutTransfer(kv.Key); - } + this.PutTransfers(xdbTransaction, tracker.Select(kv => kv.Key).ToArray()); + this.SaveNextMatureHeight(xdbTransaction, newChainATip); - xdbTransaction.SaveNextMatureHeight(newChainATip); xdbTransaction.Commit(); bool walletUpdated = false; @@ -444,14 +441,7 @@ public Task RecordLatestMatureDepositsAsync(IList kv in tracker) - { - ICrossChainTransfer transfer = kv.Key; - - this.logger.LogTrace("Registering transfer: {0}.", transfer); - - xdbTransaction.PutTransfer(transfer); - } + this.PutTransfers(xdbTransaction, tracker.Select(kv => kv.Key).ToArray()); // Ensure we get called for a retry by NOT advancing the chain A tip if the block // contained any suspended transfers. From 846abf296d18b0fc384d9651d70eec951ca4b343 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 17:34:02 +1100 Subject: [PATCH 12/15] Fix select --- .../TargetChain/CrossChainDBTransaction.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index c82fa6b5..0ce364dd 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -85,7 +85,14 @@ private bool Select(string tableName, TKey key, out TObject obj) { byte[] keyBytes = this.dBreezeSerializer.Serialize(key); Row row = this.transaction.Select(tableName, keyBytes); - obj = row.Exists ? this.dBreezeSerializer.Deserialize(row.Value) : default(TObject); + + if (!row.Exists) + { + obj = default(TObject); + return false; + } + + obj = this.dBreezeSerializer.Deserialize(row.Value); // If this is a tracked class. if (this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) @@ -94,7 +101,7 @@ private bool Select(string tableName, TKey key, out TObject obj) tracker.SetOldValue(obj); } - return row.Exists; + return true; } private IEnumerable SelectForward(string tableName) where TObject : IBitcoinSerializable @@ -134,7 +141,7 @@ private void RemoveKey(string tableName, TKey key, TObject obj) w public ICrossChainTransfer GetTransfer(uint256 depositId) { - if (!Select(CrossChainDB.TransferTableName, depositId, out ICrossChainTransfer crossChainTransfer)) + if (!Select(CrossChainDB.TransferTableName, depositId, out CrossChainTransfer crossChainTransfer)) return null; return crossChainTransfer; @@ -142,7 +149,7 @@ public ICrossChainTransfer GetTransfer(uint256 depositId) public IEnumerable EnumerateTransfers() { - foreach (ICrossChainTransfer crossChainTransfer in SelectForward(CrossChainDB.TransferTableName)) + foreach (ICrossChainTransfer crossChainTransfer in SelectForward(CrossChainDB.TransferTableName)) yield return crossChainTransfer; } From 626e08d66de1d6f9e4a8bff7b7de258dca2dfd03 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 18:26:04 +1100 Subject: [PATCH 13/15] Minor refactor and workaround --- .../Interfaces/IUpdateLookups.cs | 4 ++-- .../TargetChain/CrossChainDB.cs | 10 ++++----- .../TargetChain/CrossChainDBTransaction.cs | 21 +++++++++++++------ 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs index 425d415b..b7048c52 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/IUpdateLookups.cs @@ -13,13 +13,13 @@ public interface ICrossChainLookups /// Creates trackers for recording information on how to update the lookups. /// /// Trackers for recording information on how to update the lookups. - Dictionary CreateTrackers(); + Dictionary CreateTrackers(); /// /// Updates lookups based on the changes recorded in the tracker object. /// /// Trackers recording information about how to update the lookups. /// This method is intended be called by the class. - void UpdateLookups(Dictionary trackers); + void UpdateLookups(Dictionary trackers); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs index c4ffe58f..a3da417e 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs @@ -312,19 +312,19 @@ protected void SaveNextMatureHeight(CrossChainDBTransaction xdbTransaction, int } /// - public Dictionary CreateTrackers() + public Dictionary CreateTrackers() { - var trackers = new Dictionary(); + var trackers = new Dictionary(); - trackers[typeof(ICrossChainTransfer)] = new StatusChangeTracker(); + trackers[TransferTableName] = new StatusChangeTracker(); return trackers; } /// - public void UpdateLookups(Dictionary trackers) + public void UpdateLookups(Dictionary trackers) { - StatusChangeTracker tracker = (StatusChangeTracker)trackers[typeof(ICrossChainTransfer)]; + StatusChangeTracker tracker = (StatusChangeTracker)trackers[TransferTableName]; foreach (uint256 hash in tracker.UniqueBlockHashes()) { diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index 0ce364dd..ec676147 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -35,7 +35,7 @@ public class CrossChainDBTransaction : IDisposable private readonly CrossChainTransactionMode mode; /// Tracking changes allows updating of transient lookups after a successful commit operation. - private Dictionary trackers; + private Dictionary trackers; /// /// Constructs a transaction object that acts as a wrapper around the database tables. @@ -74,7 +74,7 @@ private void Insert(string tableName, TKey key, TObject obj) wher this.transaction.Insert(tableName, keyBytes, objBytes); // If this is a tracked class. - if (this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + if (this.trackers.TryGetValue(tableName, out IChangeTracker tracker)) { // Record the object and its old value. tracker.RecordOldValue(obj); @@ -92,10 +92,19 @@ private bool Select(string tableName, TKey key, out TObject obj) return false; } - obj = this.dBreezeSerializer.Deserialize(row.Value); + // Temporary workaround until DBreezeSerializer is fixed. + if (typeof(ICrossChainTransfer).IsAssignableFrom(typeof(TObject))) + { + obj = (TObject)Activator.CreateInstance(typeof(CrossChainTransfer)); + obj.ReadWrite(row.Value, this.dBreezeSerializer.Network.Consensus.ConsensusFactory); + } + else + { + obj = this.dBreezeSerializer.Deserialize(row.Value); + } // If this is a tracked class. - if (this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + if (this.trackers.TryGetValue(tableName, out IChangeTracker tracker)) { // Set the old value on the object itself so that we can update the lookups if it is changed. tracker.SetOldValue(obj); @@ -106,7 +115,7 @@ private bool Select(string tableName, TKey key, out TObject obj) private IEnumerable SelectForward(string tableName) where TObject : IBitcoinSerializable { - if (!this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + if (!this.trackers.TryGetValue(tableName, out IChangeTracker tracker)) tracker = null; foreach (Row row in this.transaction.SelectForward(tableName)) @@ -132,7 +141,7 @@ private void RemoveKey(string tableName, TKey key, TObject obj) w this.transaction.RemoveKey(tableName, keyBytes); // If this is a tracked class. - if (!this.trackers.TryGetValue(typeof(TObject), out IChangeTracker tracker)) + if (!this.trackers.TryGetValue(tableName, out IChangeTracker tracker)) { // Record the object and its old value. tracker.RecordOldValue(obj); From 857143774e8bf0971bb4ed7d4ea9ef08465257bc Mon Sep 17 00:00:00 2001 From: quantumagi Date: Fri, 4 Jan 2019 19:16:11 +1100 Subject: [PATCH 14/15] Fix SelectForward --- .../TargetChain/CrossChainDBTransaction.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index ec676147..7b419ff7 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -120,7 +120,18 @@ private IEnumerable SelectForward(string tableName) wher foreach (Row row in this.transaction.SelectForward(tableName)) { - TObject obj = this.dBreezeSerializer.Deserialize(row.Value); + TObject obj; + + // Temporary workaround until DBreezeSerializer is fixed. + if (typeof(ICrossChainTransfer).IsAssignableFrom(typeof(TObject))) + { + obj = (TObject)Activator.CreateInstance(typeof(CrossChainTransfer)); + obj.ReadWrite(row.Value, this.dBreezeSerializer.Network.Consensus.ConsensusFactory); + } + else + { + obj = this.dBreezeSerializer.Deserialize(row.Value); + } // If this is a tracked class. if (tracker != null) From fa0361bb25c4f4011382f2227a3ea50d6130ea92 Mon Sep 17 00:00:00 2001 From: quantumagi Date: Wed, 23 Jan 2019 15:03:44 +1100 Subject: [PATCH 15/15] Changes based on feedback --- .../Interfaces/ICrossChainDB.cs | 6 ++--- .../TargetChain/CrossChainDB.cs | 2 +- .../TargetChain/CrossChainDBTransaction.cs | 22 +++++++++---------- .../TargetChain/CrossChainTransferStore.cs | 16 +++++++------- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs index 3a0fe190..8631a38f 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/Interfaces/ICrossChainDB.cs @@ -11,9 +11,9 @@ public interface ICrossChainDB : ICrossChainLookups, IDisposable /// /// Creates a for either read or read/write operations. /// - /// The mode which is either - /// or + /// The mode which is either + /// or /// The object. - CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read); + CrossChainDBTransaction GetTransaction(CrossChainDBTransactionMode mode = CrossChainDBTransactionMode.Read); } } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs index a3da417e..68e09ccd 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDB.cs @@ -139,7 +139,7 @@ public void Initialize() } /// - public CrossChainDBTransaction GetTransaction(CrossChainTransactionMode mode = CrossChainTransactionMode.Read) + public CrossChainDBTransaction GetTransaction(CrossChainDBTransactionMode mode = CrossChainDBTransactionMode.Read) { CrossChainDBTransaction xdbTransaction = new CrossChainDBTransaction(this.DBreeze, this.DBreezeSerializer, (ICrossChainLookups)this, mode); diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs index 7b419ff7..2e4ee446 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainDBTransaction.cs @@ -8,7 +8,7 @@ namespace Stratis.FederatedPeg.Features.FederationGateway.TargetChain { /// Supported DBreeze transaction modes. - public enum CrossChainTransactionMode + public enum CrossChainDBTransactionMode { Read, ReadWrite @@ -17,7 +17,7 @@ public enum CrossChainTransactionMode /// /// The purpose of this class is to restrict the operations that can be performed on the underlying /// database - i.e. it provides a "higher level" layer to the underlying DBreeze transaction. - /// As such it provides a guarantees that any transient lookups will be kept in step with changes + /// As such it provides a guarantee that any transient lookups will be kept in sync with changes /// to the database. It also handles all required serialization here in one place. /// public class CrossChainDBTransaction : IDisposable @@ -32,7 +32,7 @@ public class CrossChainDBTransaction : IDisposable private readonly ICrossChainLookups crossChainLookups; /// The mode of the transaction. - private readonly CrossChainTransactionMode mode; + private readonly CrossChainDBTransactionMode mode; /// Tracking changes allows updating of transient lookups after a successful commit operation. private Dictionary trackers; @@ -48,7 +48,7 @@ public CrossChainDBTransaction( DBreeze.DBreezeEngine dbreeze, DBreezeSerializer dbreezeSerializer, ICrossChainLookups updateLookups, - CrossChainTransactionMode mode) + CrossChainDBTransactionMode mode) { this.transaction = dbreeze.GetTransaction(); this.dBreezeSerializer = dbreezeSerializer; @@ -57,7 +57,7 @@ public CrossChainDBTransaction( this.transaction.ValuesLazyLoadingIsOn = false; - if (mode == CrossChainTransactionMode.ReadWrite) + if (mode == CrossChainDBTransactionMode.ReadWrite) { this.transaction.SynchronizeTables(CrossChainDB.TransferTableName, CrossChainDB.CommonTableName); } @@ -67,7 +67,7 @@ public CrossChainDBTransaction( private void Insert(string tableName, TKey key, TObject obj) where TObject : IBitcoinSerializable { - Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + Guard.Assert(this.mode == CrossChainDBTransactionMode.ReadWrite); byte[] keyBytes = this.dBreezeSerializer.Serialize(key); byte[] objBytes = this.dBreezeSerializer.Serialize(obj); @@ -146,7 +146,7 @@ private IEnumerable SelectForward(string tableName) wher private void RemoveKey(string tableName, TKey key, TObject obj) where TObject : IBitcoinSerializable { - Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + Guard.Assert(this.mode == CrossChainDBTransactionMode.ReadWrite); byte[] keyBytes = this.dBreezeSerializer.Serialize(key); this.transaction.RemoveKey(tableName, keyBytes); @@ -193,7 +193,7 @@ public void DeleteTransfer(ICrossChainTransfer transfer) public void Commit() { - Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + Guard.Assert(this.mode == CrossChainDBTransactionMode.ReadWrite); this.transaction.Commit(); this.crossChainLookups.UpdateLookups(this.trackers); @@ -201,7 +201,7 @@ public void Commit() public void Rollback() { - Guard.Assert(this.mode == CrossChainTransactionMode.ReadWrite); + Guard.Assert(this.mode == CrossChainDBTransactionMode.ReadWrite); this.transaction.Rollback(); } @@ -225,7 +225,7 @@ public BlockLocator LoadTipHashAndHeight() public void SaveTipHashAndHeight(BlockLocator blockLocator) { - Guard.Assert(this.mode != CrossChainTransactionMode.Read); + Guard.Assert(this.mode != CrossChainDBTransactionMode.Read); this.transaction.Insert(CrossChainDB.CommonTableName, CrossChainDB.RepositoryTipKey, blockLocator.ToBytes()); } @@ -239,7 +239,7 @@ public void SaveTipHashAndHeight(BlockLocator blockLocator) public void SaveNextMatureHeight(int newTip) { - Guard.Assert(this.mode != CrossChainTransactionMode.Read); + Guard.Assert(this.mode != CrossChainDBTransactionMode.Read); this.transaction.Insert(CrossChainDB.CommonTableName, CrossChainDB.NextMatureTipKey, newTip); } diff --git a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs index 97183388..cc00a42e 100644 --- a/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs +++ b/src/Stratis.FederatedPeg.Features.FederationGateway/TargetChain/CrossChainTransferStore.cs @@ -198,7 +198,7 @@ private ICrossChainTransfer[] ValidateCrossChainTransfers(ICrossChainTransfer[] return crossChainTransfers; } - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { int oldChainATip = this.NextMatureDepositHeight; @@ -307,7 +307,7 @@ public Task SaveCurrentTipAsync() { lock (this.lockObj) { - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { xdbTransaction.SaveNextMatureHeight(this.NextMatureDepositHeight); xdbTransaction.Commit(); @@ -429,7 +429,7 @@ public Task RecordLatestMatureDepositsAsync(IList MergeTransactionSignaturesAsync(uint256 depositId, Tran return transfer.PartialTransaction; } - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { try { @@ -645,7 +645,7 @@ private void Put(List blocks) return; } - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { ChainedHeader prevTip = this.TipHashAndHeight; @@ -715,7 +715,7 @@ private bool RewindIfRequired() this.logger.LogTrace("Fork height determined to be {0}", fork.Height); - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { ChainedHeader prevTip = this.TipHashAndHeight; @@ -771,7 +771,7 @@ private bool Synchronize() if (this.SynchronizeBatch()) { - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.ReadWrite)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.ReadWrite)) { this.SaveTipHashAndHeight(xdbTransaction, this.TipHashAndHeight); @@ -848,7 +848,7 @@ public Task GetAsync(uint256[] depositIds) private ICrossChainTransfer[] Get(uint256[] depositIds) { - using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainTransactionMode.Read)) + using (CrossChainDBTransaction xdbTransaction = this.GetTransaction(CrossChainDBTransactionMode.Read)) { return this.Get(xdbTransaction, depositIds); }