public void ReceiveMaturedBlock_Should_Call_ReceivedMatureBlockDeposits() { var controller = new FederationGatewayController( this.loggerFactory, this.maturedBlockReceiver, this.maturedBlocksRequester, this.leaderProvider, this.chain, GetMaturedBlocksProvider(), this.depositExtractor, this.leaderReceiver); HashHeightPair hashHeightPair = TestingValues.GetHashHeightPair(); var deposits = new MaturedBlockDepositsModel(new MaturedBlockModel() { BlockHash = hashHeightPair.Hash, BlockHeight = hashHeightPair.Height }, new[] { new Deposit(0, Money.COIN * 10000, "TTMM7qGGxD5c77pJ8puBg7sTLAm2zZNBwK", hashHeightPair.Height, hashHeightPair.Hash) }); var callCount = 0; this.maturedBlockReceiver.When(x => x.ReceiveMaturedBlockDeposits(Arg.Any <IMaturedBlockDeposits[]>())).Do(info => { callCount++; }); controller.ReceiveMaturedBlock(deposits); callCount.Should().Be(1); }
/// <summary> /// Get the first block on this chain that has a timestamp after the deposit's block time on the counterchain. /// This is so that we can assign a block height that the deposit 'arrived' on the sidechain. /// TODO: This can probably be made more efficient than looping every time. /// </summary> /// <param name="maturedBlockDeposit">The matured block deposit's block time to check against.</param> /// <param name="potentialConversionTransaction">The conversion transaction we are currently working with.</param> /// <param name="chainedHeader">The chained header to use.</param> /// <returns><c>true</c> if found.</returns> private bool FindApplicableConversionRequestHeader(MaturedBlockDepositsModel maturedBlockDeposit, IDeposit potentialConversionTransaction, out ChainedHeader chainedHeader) { chainedHeader = this.chainIndexer.Tip; bool found = false; this.logger.LogDebug($"Finding applicable header for deposit with block time '{maturedBlockDeposit.BlockInfo.BlockTime}'; chain tip '{this.chainIndexer.Tip}'."); while (true) { if (chainedHeader == this.chainIndexer.Genesis) { break; } if (chainedHeader.Previous.Header.Time <= maturedBlockDeposit.BlockInfo.BlockTime) { found = true; break; } chainedHeader = chainedHeader.Previous; } if (!found) { this.logger.LogWarning("Unable to determine timestamp for conversion transaction '{0}', ignoring.", potentialConversionTransaction.Id); } this.logger.LogDebug($"Applicable header selected '{chainedHeader}'"); return(found); }
public void ShouldSerialiseAsJson() { MaturedBlockDepositsModel maturedBlockDeposits = TestingValues.GetMaturedBlockDeposits(3); string asJson = maturedBlockDeposits.ToString(); var reconverted = JsonConvert.DeserializeObject <MaturedBlockDepositsModel>(asJson); reconverted.BlockInfo.BlockHash.Should().Be(maturedBlockDeposits.BlockInfo.BlockHash); reconverted.BlockInfo.BlockHeight.Should().Be(maturedBlockDeposits.BlockInfo.BlockHeight); reconverted.Deposits.Should().BeEquivalentTo(maturedBlockDeposits.Deposits); }
private List <MaturedBlockDepositsModel> RetrieveDepositsFromHeight(DepositRetrievalType retrievalType, int applicableHeight, int retrieveFromHeight) { List <ChainedHeader> applicableHeaders = RetrieveApplicableHeaders(applicableHeight, retrieveFromHeight); var depositBlockModels = new List <MaturedBlockDepositsModel>(); // Half of the timeout, we will also need time to convert it to json. using (var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(RestApiClientBase.TimeoutSeconds / 2))) { for (int headerIndex = 0; headerIndex < applicableHeaders.Count; headerIndex += 100) { List <ChainedHeader> currentHeaders = applicableHeaders.GetRange(headerIndex, Math.Min(100, applicableHeaders.Count - headerIndex)); var hashes = currentHeaders.Select(h => h.HashBlock).ToList(); ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes); foreach (ChainedHeaderBlock chainedHeaderBlock in blocks) { if (chainedHeaderBlock?.Block?.Transactions == null) { this.logger.LogDebug(UnableToRetrieveBlockDataFromConsensusMessage, chainedHeaderBlock.ChainedHeader); break; } MaturedBlockDepositsModel depositBlockModel = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock, retrievalType); if (depositBlockModel != null && depositBlockModel.Deposits != null) { this.logger.LogDebug("{0} '{1}' deposits extracted at block '{2}'", depositBlockModel.Deposits.Count, retrievalType, chainedHeaderBlock.ChainedHeader); if (depositBlockModel.Deposits.Any()) { depositBlockModels.Add(depositBlockModel); } } if (depositBlockModels.Count >= MaturedBlocksSyncManager.MaxBlocksToRequest || depositBlockModels.SelectMany(d => d.Deposits).Count() >= int.MaxValue) { this.logger.LogDebug("Stopping matured blocks collection, thresholds reached; {0}={1}, numberOfDeposits={2}", nameof(depositBlockModels), depositBlockModels.Count, depositBlockModels.SelectMany(d => d.Deposits).Count()); break; } if (cancellationToken.IsCancellationRequested) { this.logger.LogDebug("Stopping matured blocks collection, the request is taking too long, sending what has been collected."); break; } } } } return(depositBlockModels); }
public static MaturedBlockDepositsModel GetMaturedBlockDeposits(int depositCount = 0, HashHeightPair fixedHashHeight = null) { HashHeightPair hashHeightPair = fixedHashHeight ?? GetHashHeightPair(); IEnumerable <IDeposit> deposits = Enumerable.Range(0, depositCount).Select(_ => GetDeposit(hashHeightPair)); var maturedBlockDeposits = new MaturedBlockDepositsModel( new MaturedBlockInfoModel() { BlockHash = hashHeightPair.Hash, BlockHeight = hashHeightPair.Height }, deposits.ToList()); return(maturedBlockDeposits); }
/// <inheritdoc /> public async Task <Result <List <MaturedBlockDepositsModel> > > GetMaturedDepositsAsync(int blockHeight, int maxBlocks) { ChainedHeader consensusTip = this.consensusManager.Tip; int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { // We need to return a Result type here to explicitly indicate failure and the reason for failure. // This is an expected condition so we can avoid throwing an exception here. return(Result <List <MaturedBlockDepositsModel> > .Fail($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed.")); } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Half of the timeout. We will also need time to convert it to json. int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2; var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs); for (int i = blockHeight; (i <= matureTipHeight) && (i < blockHeight + maxBlocks); i++) { ChainedHeader currentHeader = consensusTip.GetAncestor(i); ChainedHeaderBlock block = this.consensusManager.GetBlockData(currentHeader.HashBlock); if (block?.Block?.Transactions == null) { // Report unexpected results from consenus manager. this.logger.LogWarning("Stop matured blocks collection due to consensus manager integrity failure. Send what we've collected."); break; } MaturedBlockDepositsModel maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(block); if (maturedBlockDeposits == null) { throw new InvalidOperationException($"Unable to get deposits for block at height {currentHeader.Height}"); } maturedBlocks.Add(maturedBlockDeposits); if (cancellation.IsCancellationRequested && maturedBlocks.Count > 0) { this.logger.LogDebug("Stop matured blocks collection because it's taking too long. Send what we've collected."); break; } } return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); }
public MaturedBlockDepositsModel ExtractBlockDeposits(ChainedHeaderBlock newlyMaturedBlock) { Guard.NotNull(newlyMaturedBlock, nameof(newlyMaturedBlock)); var maturedBlock = new MaturedBlockInfoModel() { BlockHash = newlyMaturedBlock.ChainedHeader.HashBlock, BlockHeight = newlyMaturedBlock.ChainedHeader.Height, BlockTime = newlyMaturedBlock.ChainedHeader.Header.Time }; IReadOnlyList <IDeposit> deposits = this.ExtractDepositsFromBlock(newlyMaturedBlock.Block, newlyMaturedBlock.ChainedHeader.Height); var maturedBlockDeposits = new MaturedBlockDepositsModel(maturedBlock, deposits); return(maturedBlockDeposits); }
public IMaturedBlockDeposits ExtractBlockDeposits(ChainedHeader newlyMaturedBlock) { if (newlyMaturedBlock == null) { return(null); } var maturedBlock = new MaturedBlockModel() { BlockHash = newlyMaturedBlock.HashBlock, BlockHeight = newlyMaturedBlock.Height }; IReadOnlyList <IDeposit> deposits = this.ExtractDepositsFromBlock(newlyMaturedBlock.Block, newlyMaturedBlock.Height); var maturedBlockDeposits = new MaturedBlockDepositsModel(maturedBlock, deposits); return(maturedBlockDeposits); }
/// <inheritdoc /> public async Task <List <MaturedBlockDepositsModel> > GetMaturedDepositsAsync(int blockHeight, int maxBlocks) { ChainedHeader consensusTip = this.consensusManager.Tip; int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { throw new InvalidOperationException($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed."); } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Half of the timeout. We will also need time to convert it to json. int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2; var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs); for (int i = blockHeight; (i <= matureTipHeight) && (i < blockHeight + maxBlocks); i++) { ChainedHeader currentHeader = consensusTip.GetAncestor(i); ChainedHeaderBlock block = await this.consensusManager.GetBlockDataAsync(currentHeader.HashBlock).ConfigureAwait(false); MaturedBlockDepositsModel maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(block); if (maturedBlockDeposits == null) { throw new InvalidOperationException($"Unable to get deposits for block at height {currentHeader.Height}"); } maturedBlocks.Add(maturedBlockDeposits); if (cancellation.IsCancellationRequested && maturedBlocks.Count > 0) { this.logger.LogDebug("Stop matured blocks collection because it's taking too long. Send what we've collected."); break; } } return(maturedBlocks); }
/// <inheritdoc /> public SerializableResult <List <MaturedBlockDepositsModel> > GetMaturedDeposits(int retrieveFromHeight, int maxBlocks, int maxDeposits = int.MaxValue) { ChainedHeader consensusTip = this.consensusManager.Tip; if (consensusTip == null) { return(SerializableResult <List <MaturedBlockDepositsModel> > .Fail("Consensus is not ready to provide blocks.")); } int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (retrieveFromHeight > matureTipHeight) { this.logger.LogTrace("(-)[RETRIEVEFROMBLOCK_HIGHER_THAN_MATUREDTIP]:{0}={1},{2}={3}", nameof(retrieveFromHeight), retrieveFromHeight, nameof(matureTipHeight), matureTipHeight); return(SerializableResult <List <MaturedBlockDepositsModel> > .Fail(string.Format(RetrieveBlockHeightHigherThanMaturedTipMessage, retrieveFromHeight, matureTipHeight))); } var maturedBlockDepositModels = new List <MaturedBlockDepositsModel>(); // Half of the timeout. We will also need time to convert it to json. int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2; var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs); int maxBlockHeight = Math.Min(matureTipHeight, retrieveFromHeight + maxBlocks - 1); var headers = new List <ChainedHeader>(); ChainedHeader header = consensusTip.GetAncestor(maxBlockHeight); for (int i = maxBlockHeight; i >= retrieveFromHeight; i--) { headers.Add(header); header = header.Previous; } headers.Reverse(); int numberOfDeposits = 0; for (int headerIndex = 0; headerIndex < headers.Count; headerIndex += 100) { List <ChainedHeader> currentHeaders = headers.GetRange(headerIndex, Math.Min(100, headers.Count - headerIndex)); var hashes = currentHeaders.Select(h => h.HashBlock).ToList(); ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes); foreach (ChainedHeaderBlock chainedHeaderBlock in blocks) { if (chainedHeaderBlock?.Block?.Transactions == null) { this.logger.LogDebug(UnableToRetrieveBlockDataFromConsensusMessage, chainedHeaderBlock.ChainedHeader); this.logger.LogTrace("(-)[BLOCKDATA_MISSING_FROM_CONSENSUS]"); return(SerializableResult <List <MaturedBlockDepositsModel> > .Ok(maturedBlockDepositModels, string.Format(UnableToRetrieveBlockDataFromConsensusMessage, chainedHeaderBlock.ChainedHeader))); } MaturedBlockDepositsModel maturedBlockDepositModel = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock); if (maturedBlockDepositModel.Deposits != null && maturedBlockDepositModel.Deposits.Count > 0) { this.logger.LogDebug("{0} deposits extracted at block {1}", maturedBlockDepositModel.Deposits.Count, chainedHeaderBlock.ChainedHeader); } maturedBlockDepositModels.Add(maturedBlockDepositModel); numberOfDeposits += maturedBlockDepositModel.Deposits?.Count ?? 0; if (maturedBlockDepositModels.Count >= maxBlocks || numberOfDeposits >= maxDeposits) { this.logger.LogDebug("Stopping matured blocks collection, thresholds reached; {0}={1}, {2}={3}", nameof(maturedBlockDepositModels), maturedBlockDepositModels.Count, nameof(numberOfDeposits), numberOfDeposits); return(SerializableResult <List <MaturedBlockDepositsModel> > .Ok(maturedBlockDepositModels)); } if (cancellation.IsCancellationRequested) { this.logger.LogDebug("Stopping matured blocks collection, the request is taking too long. Sending what has been collected."); return(SerializableResult <List <MaturedBlockDepositsModel> > .Ok(maturedBlockDepositModels)); } } } return(SerializableResult <List <MaturedBlockDepositsModel> > .Ok(maturedBlockDepositModels)); }
public void ReceiveMaturedBlock([FromBody] MaturedBlockDepositsModel maturedBlockDeposits) { this.maturedBlockReceiver.ReceiveMaturedBlockDeposits(new[] { maturedBlockDeposits }); }
/// <inheritdoc /> public async Task <Result <List <MaturedBlockDepositsModel> > > GetMaturedDepositsAsync(int blockHeight, int maxBlocks, int maxDeposits = int.MaxValue) { ChainedHeader consensusTip = this.consensusManager.Tip; if (consensusTip == null) { return(Result <List <MaturedBlockDepositsModel> > .Fail("Not ready to provide blocks.")); } int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { // We need to return a Result type here to explicitly indicate failure and the reason for failure. // This is an expected condition so we can avoid throwing an exception here. return(Result <List <MaturedBlockDepositsModel> > .Fail($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed.")); } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Half of the timeout. We will also need time to convert it to json. int maxTimeCollectionCanTakeMs = RestApiClientBase.TimeoutMs / 2; var cancellation = new CancellationTokenSource(maxTimeCollectionCanTakeMs); int maxBlockHeight = Math.Min(matureTipHeight, blockHeight + maxBlocks - 1); var headers = new List <ChainedHeader>(); ChainedHeader header = consensusTip.GetAncestor(maxBlockHeight); for (int i = maxBlockHeight; i >= blockHeight; i--) { headers.Add(header); header = header.Previous; } headers.Reverse(); int numDeposits = 0; for (int ndx = 0; ndx < headers.Count; ndx += 100) { List <ChainedHeader> currentHeaders = headers.GetRange(ndx, Math.Min(100, headers.Count - ndx)); List <uint256> hashes = currentHeaders.Select(h => h.HashBlock).ToList(); ChainedHeaderBlock[] blocks = this.consensusManager.GetBlockData(hashes); foreach (ChainedHeaderBlock chainedHeaderBlock in blocks) { if (chainedHeaderBlock?.Block?.Transactions == null) { this.logger.LogDebug("Unexpected null data. Send what we've collected."); return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } MaturedBlockDepositsModel maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(chainedHeaderBlock); maturedBlocks.Add(maturedBlockDeposits); numDeposits += maturedBlockDeposits.Deposits?.Count ?? 0; if (maturedBlocks.Count >= maxBlocks || numDeposits >= maxDeposits) { return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } if (cancellation.IsCancellationRequested) { this.logger.LogDebug("Stop matured blocks collection because it's taking too long. Send what we've collected."); return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); } } } return(Result <List <MaturedBlockDepositsModel> > .Ok(maturedBlocks)); }
/// <inheritdoc /> public async Task <List <MaturedBlockDepositsModel> > GetMaturedDepositsAsync(int blockHeight, int maxBlocks) { ChainedHeader consensusTip = this.consensusManager.Tip; int matureTipHeight = (consensusTip.Height - (int)this.depositExtractor.MinimumDepositConfirmations); if (blockHeight > matureTipHeight) { throw new InvalidOperationException($"Block height {blockHeight} submitted is not mature enough. Blocks less than a height of {matureTipHeight} can be processed."); } // Cache clean-up. lock (this.depositCache) { // The requested height gives away the fact that the peer is probably no longer interested in cached entries below that height. // Keep an additional 1,000 blocks anyway in case there are some parallel request that are still executing for lower heights. foreach (int i in this.depositCache.Where(d => d.Key < (blockHeight - 1000)).Select(d => d.Key).ToArray()) { this.depositCache.Remove(i); } } var maturedBlocks = new List <MaturedBlockDepositsModel>(); // Don't spend to much time that the requester may give up. DateTime deadLine = DateTime.Now.AddSeconds(30); for (int i = blockHeight; (i <= matureTipHeight) && (i < blockHeight + maxBlocks); i++) { MaturedBlockDepositsModel maturedBlockDeposits = null; // First try the cache. lock (this.depositCache) { this.depositCache.TryGetValue(i, out maturedBlockDeposits); } // If not in cache.. if (maturedBlockDeposits == null) { ChainedHeader currentHeader = consensusTip.GetAncestor(i); ChainedHeaderBlock block = await this.consensusManager.GetBlockDataAsync(currentHeader.HashBlock).ConfigureAwait(false); maturedBlockDeposits = this.depositExtractor.ExtractBlockDeposits(block); if (maturedBlockDeposits == null) { throw new InvalidOperationException($"Unable to get deposits for block at height {currentHeader.Height}"); } // Save this so that we don't need to scan the block again. lock (this.depositCache) { this.depositCache[i] = maturedBlockDeposits; } } maturedBlocks.Add(maturedBlockDeposits); if (DateTime.Now >= deadLine) { break; } } return(maturedBlocks); }