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);
        }
Beispiel #2
0
        /// <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);
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        /// <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);
        }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        /// <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));
        }
Beispiel #11
0
 public void ReceiveMaturedBlock([FromBody] MaturedBlockDepositsModel maturedBlockDeposits)
 {
     this.maturedBlockReceiver.ReceiveMaturedBlockDeposits(new[] { maturedBlockDeposits });
 }
Beispiel #12
0
        /// <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);
        }