Exemplo n.º 1
0
        /// <summary>Asks for blocks from another gateway node and then processes them.</summary>
        /// <returns><c>true</c> if delay between next time we should ask for blocks is required; <c>false</c> otherwise.</returns>
        protected async Task <bool> SyncDepositsAsync()
        {
            // First ensure that the federation wallet is active.
            if (!this.federationWalletManager.IsFederationWalletActive())
            {
                this.logger.LogInformation("The CCTS will start processing deposits once the federation wallet has been activated.");
                return(true);
            }

            // Then ensure that the node is out of IBD.
            if (this.initialBlockDownloadState.IsInitialBlockDownload())
            {
                this.logger.LogInformation("The CCTS will start processing deposits once the node is out of IBD.");
                return(true);
            }

            // Then ensure that the federation wallet is synced with the chain.
            if (!this.federationWalletManager.IsSyncedWithChain())
            {
                this.logger.LogInformation($"The CCTS will start processing deposits once the federation wallet is synced with the chain; height {this.federationWalletManager.WalletTipHeight}");
                return(true);
            }

            this.logger.LogInformation($"Requesting deposits from counterchain node.");

            SerializableResult <List <MaturedBlockDepositsModel> > matureBlockDeposits = await this.federationGatewayClient.GetMaturedBlockDepositsAsync(this.crossChainTransferStore.NextMatureDepositHeight, this.nodeLifetime.ApplicationStopping).ConfigureAwait(false);

            if (matureBlockDeposits == null)
            {
                this.logger.LogDebug("Failed to fetch normal deposits from counter chain node; {0} didn't respond.", this.federationGatewayClient.EndpointUrl);
                return(true);
            }

            if (matureBlockDeposits.Value == null)
            {
                this.logger.LogDebug("Failed to fetch normal deposits from counter chain node; {0} didn't reply with any deposits; Message: {1}", this.federationGatewayClient.EndpointUrl, matureBlockDeposits.Message ?? "none");
                return(true);
            }

            return(await ProcessMatureBlockDepositsAsync(matureBlockDeposits).ConfigureAwait(false));
        }
        public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario2()
        {
            // Create a "chain" of 30 blocks.
            this.blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(30, true, this.mainChainNetwork);

            // Add 6 normal deposits to block 11 through to 16.
            for (int i = 11; i < 17; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            // Add 4 small deposits to blocks 5 through to 9 (the amounts are less than 10).
            for (int i = 5; i < 9; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            this.consensusManager.GetBlockData(Arg.Any<List<uint256>>()).Returns(delegate (CallInfo info)
            {
                var hashes = (List<uint256>)info[0];
                return hashes.Select((hash) => this.blocks.Single(x => x.ChainedHeader.HashBlock == hash)).ToArray();
            });
            this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader);

            var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader);

            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings);

            SerializableResult<List<MaturedBlockDepositsModel>> depositsResult = maturedBlocksProvider.RetrieveDeposits(5);

            // Total deposits
            Assert.Equal(10, depositsResult.Value.SelectMany(b => b.Deposits).Count());

            // Normal Deposits
            Assert.Equal(6, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Normal).Count());

            // Small Deposits
            Assert.Equal(4, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Small).Count());
        }
Exemplo n.º 3
0
        public void RetrieveDeposits_ReturnsFasterAndNormalDeposits_Scenario3()
        {
            // Create a "chain" of 30 blocks.
            List <ChainedHeaderBlock> blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(30, null, true);

            // Add 6 faster deposits to blocks 8 through to 13 (the amounts are less than 10).
            for (int i = 8; i <= 13; i++)
            {
                blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, blocks[i].Block, Money.Coins(8), this.opReturnBytes);
            }

            // Add 5 normal deposits to block 11 through to 15.
            for (int i = 11; i <= 15; i++)
            {
                blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            this.consensusManager.GetBlockData(Arg.Any <List <uint256> >()).Returns(delegate(CallInfo info)
            {
                var hashes = (List <uint256>)info[0];
                return(hashes.Select((hash) => blocks.Single(x => x.ChainedHeader.HashBlock == hash)).ToArray());
            });
            this.consensusManager.Tip.Returns(blocks.Last().ChainedHeader);

            var depositExtractor = new DepositExtractor(this.loggerFactory, this.federatedPegSettings, this.opReturnDataReader);

            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings, this.loggerFactory);

            SerializableResult <List <MaturedBlockDepositsModel> > depositsResult = maturedBlocksProvider.RetrieveDeposits(5);

            // Total deposits
            Assert.Equal(11, depositsResult.Value.SelectMany(b => b.Deposits).Count());

            // Faster Deposits
            Assert.Equal(6, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Faster).Count());

            // Normal Deposits
            Assert.Equal(5, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Normal).Count());
        }
        public IActionResult GetMaturedBlockDeposits([FromBody] MaturedBlockRequestModel blockRequest)
        {
            Guard.NotNull(blockRequest, nameof(blockRequest));

            if (!this.ModelState.IsValid)
            {
                IEnumerable <string> errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors)));
            }

            try
            {
                SerializableResult <List <MaturedBlockDepositsModel> > depositsResult = this.maturedBlocksProvider.GetMaturedDeposits(blockRequest.BlockHeight, blockRequest.MaxBlocksToSend);
                return(this.Json(depositsResult));
            }
            catch (Exception e)
            {
                this.logger.LogDebug("Exception thrown calling /api/FederationGateway/{0}: {1}.", FederationGatewayRouteEndPoint.GetMaturedBlockDeposits, e.Message);
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, $"Could not re-sync matured block deposits: {e.Message}", e.ToString()));
            }
        }
        public async Task BlocksAreRequestedIfThereIsSomethingToRequestAsync()
        {
            this.crossChainTransferStore.NextMatureDepositHeight.Returns(5);
            this.crossChainTransferStore.RecordLatestMatureDepositsAsync(null).ReturnsForAnyArgs(new RecordLatestMatureDepositsResult().Succeeded());

            var models = new List <MaturedBlockDepositsModel>()
            {
                new MaturedBlockDepositsModel(new MaturedBlockInfoModel(), new List <IDeposit>())
            };
            var result = SerializableResult <List <MaturedBlockDepositsModel> > .Ok(models);

            this.federationGatewayClient.GetMaturedBlockDepositsAsync(0).ReturnsForAnyArgs(Task.FromResult(result));

            bool delayRequired = await this.syncManager.ExposedSyncBatchOfBlocksAsync();

            // Delay shouldn't be required because a non-empty list was provided.
            Assert.False(delayRequired);

            // Now provide empty list.
            result = SerializableResult <List <MaturedBlockDepositsModel> > .Ok(new List <MaturedBlockDepositsModel>() { });

            this.federationGatewayClient.GetMaturedBlockDepositsAsync(0).ReturnsForAnyArgs(Task.FromResult(result));

            bool delayRequired2 = await this.syncManager.ExposedSyncBatchOfBlocksAsync();

            // Delay is required because an empty list was provided.
            Assert.True(delayRequired2);

            // Now provide null.
            result = SerializableResult <List <MaturedBlockDepositsModel> > .Ok(null as List <MaturedBlockDepositsModel>);

            this.federationGatewayClient.GetMaturedBlockDepositsAsync(0).ReturnsForAnyArgs(Task.FromResult(result));

            bool delayRequired3 = await this.syncManager.ExposedSyncBatchOfBlocksAsync();

            // Delay is required because a null list was provided.
            Assert.True(delayRequired3);
        }
        public void GetMaturedBlocksReturnsDeposits()
        {
            List <ChainedHeader> headers = ChainedHeadersHelper.CreateConsecutiveHeaders(10, null, true);

            foreach (ChainedHeader chainedHeader in headers)
            {
                chainedHeader.Block = new Block(chainedHeader.Header);
            }

            var blocks = new List <ChainedHeaderBlock>(headers.Count);

            foreach (ChainedHeader chainedHeader in headers)
            {
                blocks.Add(new ChainedHeaderBlock(chainedHeader.Block, chainedHeader));
            }

            ChainedHeader tip = headers.Last();

            this.consensusManager.GetBlockData(Arg.Any <List <uint256> >()).Returns(delegate(CallInfo info)
            {
                List <uint256> hashes = (List <uint256>)info[0];
                return(hashes.Select((hash) => blocks.Single(x => x.ChainedHeader.HashBlock == hash)).ToArray());
            });

            uint zero = 0;

            this.depositExtractor.MinimumDepositConfirmations.Returns(info => zero);
            this.depositExtractor.ExtractBlockDeposits(null).ReturnsForAnyArgs(new MaturedBlockDepositsModel(new MaturedBlockInfoModel(), new List <IDeposit>()));
            this.consensusManager.Tip.Returns(tip);

            // Makes every block a matured block.
            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, this.depositExtractor, this.loggerFactory);

            SerializableResult <List <MaturedBlockDepositsModel> > depositsResult = maturedBlocksProvider.GetMaturedDeposits(0, 10);

            // Expect the number of matured deposits to equal the number of blocks.
            Assert.Equal(10, depositsResult.Value.Count);
        }
        public void GetMaturedBlocksReturnsDeposits()
        {
            this.blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(10, true, this.mainChainNetwork);

            ChainedHeader tip = this.blocks.Last().ChainedHeader;

            IFederatedPegSettings federatedPegSettings = Substitute.For<IFederatedPegSettings>();
            federatedPegSettings.MinimumConfirmationsNormalDeposits.Returns(0);

            var deposits = new List<IDeposit>() { new Deposit(new uint256(0), DepositRetrievalType.Normal, 100, "test", 0, new uint256(1)) };

            // Set the first block up to return 100 normal deposits.
            IDepositExtractor depositExtractor = Substitute.For<IDepositExtractor>();
            depositExtractor.ExtractDepositsFromBlock(this.blocks.First().Block, this.blocks.First().ChainedHeader.Height, new[] { DepositRetrievalType.Normal }).ReturnsForAnyArgs(deposits);
            this.consensusManager.Tip.Returns(tip);

            // Makes every block a matured block.
            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, federatedPegSettings);

            SerializableResult<List<MaturedBlockDepositsModel>> depositsResult = maturedBlocksProvider.RetrieveDeposits(0);

            Assert.Equal(11, depositsResult.Value.Count);
        }
Exemplo n.º 8
0
        public void RetrieveDeposits_ReturnsLargeDeposits_Scenario6()
        {
            // Create a "chain" of 20 blocks.
            this.blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(20, true, this.mainChainNetwork);

            // Add 4 small deposits to blocks 5 through to 8 (the amounts are less than 10).
            for (int i = 5; i <= 8; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            // Add 6 normal deposits to block 11 through to 16.
            for (int i = 11; i <= 16; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            // Add 6 large deposits to block 11 through to 16.
            for (int i = 11; i <= 16; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins((long)this.federatedPegSettings.NormalDepositThresholdAmount + 1), this.opReturnBytes);
            }

            this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader);

            var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader);

            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings);

            SerializableResult <List <MaturedBlockDepositsModel> > depositsResult = maturedBlocksProvider.RetrieveDeposits(10);

            // Total deposits
            Assert.Equal(4, depositsResult.Value.SelectMany(b => b.Deposits).Count());
        }
Exemplo n.º 9
0
        public void RetrieveDeposits_ReturnsSmallAndNormalDeposits_Scenario3()
        {
            // Create a "chain" of 30 blocks.
            this.blocks = ChainedHeadersHelper.CreateConsecutiveHeadersAndBlocks(30, true, this.mainChainNetwork);

            // Add 6 small deposits to blocks 8 through to 13 (the amounts are less than 10).
            for (int i = 8; i <= 13; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(8), this.opReturnBytes);
            }

            // Add 5 normal deposits to block 11 through to 15.
            for (int i = 11; i <= 15; i++)
            {
                this.blocks[i].Block.AddTransaction(new Transaction());
                CreateDepositTransaction(this.targetAddress, this.blocks[i].Block, Money.Coins(i), this.opReturnBytes);
            }

            this.consensusManager.Tip.Returns(this.blocks.Last().ChainedHeader);

            var depositExtractor = new DepositExtractor(this.federatedPegSettings, this.network, this.opReturnDataReader);

            var maturedBlocksProvider = new MaturedBlocksProvider(this.consensusManager, depositExtractor, this.federatedPegSettings);

            SerializableResult <List <MaturedBlockDepositsModel> > depositsResult = maturedBlocksProvider.RetrieveDeposits(5);

            // Total deposits
            Assert.Equal(11, depositsResult.Value.SelectMany(b => b.Deposits).Count());

            // Small Deposits
            Assert.Equal(6, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Small).Count());

            // Normal Deposits
            Assert.Equal(5, depositsResult.Value.SelectMany(b => b.Deposits).Where(d => d.RetrievalType == DepositRetrievalType.Normal).Count());
        }
        public async Task ReturnsNullIfCounterChainNodeIsOfflineAsync()
        {
            SerializableResult <List <MaturedBlockDepositsModel> > result = await this.client.GetMaturedBlockDepositsAsync(100);

            Assert.Null(result);
        }
Exemplo n.º 11
0
        private void RetrieveDeposits(DepositRetrievalType retrievalType, int retrieveFromHeight, StringBuilder messageBuilder, SerializableResult <List <MaturedBlockDepositsModel> > result)
        {
            var retrieveUpToHeight = DetermineApplicableRetrievalHeight(retrievalType, retrieveFromHeight, out string message);

            if (retrieveUpToHeight == null)
            {
                this.logger.LogDebug(message);
                messageBuilder.AppendLine(message);
            }
            else
            {
                List <MaturedBlockDepositsModel> deposits = RetrieveDepositsFromHeight(retrievalType, retrieveFromHeight, retrieveUpToHeight.Value);
                if (deposits.Any())
                {
                    result.Value.AddRange(deposits);
                }
            }
        }
        /// <inheritdoc />
        public async Task <SerializableResult <List <MaturedBlockDepositsModel> > > RetrieveDepositsAsync(int maturityHeight)
        {
            if (this.consensusManager.Tip == null)
            {
                return(SerializableResult <List <MaturedBlockDepositsModel> > .Fail("Consensus is not ready to provide blocks (it is un-initialized or still starting up)."));
            }

            var result = new SerializableResult <List <MaturedBlockDepositsModel> >
            {
                Value   = new List <MaturedBlockDepositsModel>(),
                Message = ""
            };

            // If we're asked for blocks beyond the tip then let the caller know that there are no new blocks available.
            if (maturityHeight > this.consensusManager.Tip.Height)
            {
                return(result);
            }

            int maxConfirmations = this.retrievalTypeConfirmations.Values.Max();
            int startHeight      = maturityHeight - maxConfirmations;

            // Determine the first block to extract deposits for.
            ChainedHeader firstToProcess = this.consensusManager.Tip.GetAncestor(maturityHeight);

            for (ChainedHeader verifyBlock = firstToProcess?.Previous; verifyBlock != null && verifyBlock.Height >= startHeight; verifyBlock = verifyBlock.Previous)
            {
                if (!this.deposits.TryGetValue(verifyBlock.Height, out BlockDeposits blockDeposits) || blockDeposits.BlockHash != verifyBlock.HashBlock)
                {
                    firstToProcess = verifyBlock;
                }
            }

            var cancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(RestApiClientBase.TimeoutSeconds / 2));

            // Process the blocks after the previous block until the last available block or time expires.
            foreach (ChainedHeaderBlock chainedHeaderBlock in this.consensusManager.GetBlocksAfterBlock(firstToProcess?.Previous, MaturedBlocksBatchSize, cancellationToken))
            {
                // Find all deposits in the given block.
                await RecordBlockDepositsAsync(chainedHeaderBlock, this.retrievalTypeConfirmations).ConfigureAwait(false);

                // Don't process blocks below the requested maturity height.
                if (chainedHeaderBlock.ChainedHeader.Height < maturityHeight)
                {
                    this.logger.LogDebug("{0} below maturity height of {1}.", chainedHeaderBlock.ChainedHeader, maturityHeight);
                    continue;
                }

                var maturedDeposits = new List <IDeposit>();

                // Inspect the deposits in the block for each retrieval type (validate against the retrieval type's confirmation requirement).
                foreach ((DepositRetrievalType retrievalType, int requiredConfirmations) in this.retrievalTypeConfirmations)
                {
                    // If the block height is more than the required confirmations, then the potential deposits
                    // contained within are valid for the given retrieval type.
                    if (chainedHeaderBlock.ChainedHeader.Height > requiredConfirmations)
                    {
                        maturedDeposits.AddRange(this.RecallBlockDeposits(chainedHeaderBlock.ChainedHeader.Height - requiredConfirmations, retrievalType));
                    }
                }

                this.logger.LogDebug("{0} mature deposits retrieved from block '{1}'.", maturedDeposits.Count, chainedHeaderBlock.ChainedHeader);

                result.Value.Add(new MaturedBlockDepositsModel(new MaturedBlockInfoModel()
                {
                    BlockHash   = chainedHeaderBlock.ChainedHeader.HashBlock,
                    BlockHeight = chainedHeaderBlock.ChainedHeader.Height,
                    BlockTime   = chainedHeaderBlock.ChainedHeader.Header.Time
                }, maturedDeposits));

                // Clean-up.
                this.deposits.TryRemove(chainedHeaderBlock.ChainedHeader.Height - maxConfirmations, out _);
            }

            return(result);
        }
        /// <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));
        }
Exemplo n.º 14
0
        private async Task <bool> ProcessMatureBlockDepositsAsync(SerializableResult <List <MaturedBlockDepositsModel> > matureBlockDeposits)
        {
            // "Value"'s count will be 0 if we are using NewtonSoft's serializer, null if using .Net Core 3's serializer.
            if (matureBlockDeposits.Value.Count == 0)
            {
                this.logger.LogDebug("Considering ourselves fully synced since no blocks were received.");

                // If we've received nothing we assume we are at the tip and should flush.
                // Same mechanic as with syncing headers protocol.
                await this.crossChainTransferStore.SaveCurrentTipAsync().ConfigureAwait(false);

                return(true);
            }

            this.logger.LogInformation("Processing {0} matured blocks.", matureBlockDeposits.Value.Count);

            // Filter out conversion transactions & also log what we've received for diagnostic purposes.
            foreach (MaturedBlockDepositsModel maturedBlockDeposit in matureBlockDeposits.Value)
            {
                var tempDepositList = new List <IDeposit>();

                if (maturedBlockDeposit.Deposits.Count > 0)
                {
                    this.logger.LogDebug("Matured deposit count for block {0} height {1}: {2}.", maturedBlockDeposit.BlockInfo.BlockHash, maturedBlockDeposit.BlockInfo.BlockHeight, maturedBlockDeposit.Deposits.Count);
                }

                foreach (IDeposit potentialConversionTransaction in maturedBlockDeposit.Deposits)
                {
                    // If this is not a conversion transaction then add it immediately to the temporary list.
                    if (potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionSmall &&
                        potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionNormal &&
                        potentialConversionTransaction.RetrievalType != DepositRetrievalType.ConversionLarge)
                    {
                        tempDepositList.Add(potentialConversionTransaction);
                        continue;
                    }

                    if (this.federatedPegSettings.IsMainChain)
                    {
                        this.logger.LogWarning("Conversion transactions do not get actioned by the main chain.");
                        continue;
                    }

                    var interFluxV2MainChainActivationHeight = ((PoAConsensusOptions)this.network.Consensus.Options).InterFluxV2MainChainActivationHeight;
                    if (interFluxV2MainChainActivationHeight != 0 && maturedBlockDeposit.BlockInfo.BlockHeight < interFluxV2MainChainActivationHeight)
                    {
                        this.logger.LogWarning("Conversion transactions '{0}' will not be processed below the main chain activation height of {1}.", potentialConversionTransaction.Id, interFluxV2MainChainActivationHeight);
                        continue;
                    }

                    this.logger.LogInformation("Conversion transaction '{0}' received.", potentialConversionTransaction.Id);

                    ChainedHeader applicableHeader = null;
                    bool          conversionExists = false;
                    if (this.conversionRequestRepository.Get(potentialConversionTransaction.Id.ToString()) != null)
                    {
                        this.logger.LogWarning("Conversion transaction '{0}' already exists, ignoring.", potentialConversionTransaction.Id);
                        conversionExists = true;
                    }
                    else
                    {
                        // This should ony happen if the conversion does't exist yet.
                        if (!FindApplicableConversionRequestHeader(maturedBlockDeposit, potentialConversionTransaction, out applicableHeader))
                        {
                            continue;
                        }
                    }

                    InteropConversionRequestFee interopConversionRequestFee = await this.conversionRequestFeeService.AgreeFeeForConversionRequestAsync(potentialConversionTransaction.Id.ToString(), maturedBlockDeposit.BlockInfo.BlockHeight).ConfigureAwait(false);

                    // If a dynamic fee could not be determined, create a fallback fee.
                    if (interopConversionRequestFee == null ||
                        (interopConversionRequestFee != null && interopConversionRequestFee.State != InteropFeeState.AgreeanceConcluded))
                    {
                        interopConversionRequestFee.Amount = ConversionRequestFeeService.FallBackFee;
                        this.logger.LogWarning($"A dynamic fee for conversion request '{potentialConversionTransaction.Id}' could not be determined, using a fixed fee of {ConversionRequestFeeService.FallBackFee} STRAX.");
                    }

                    if (Money.Satoshis(interopConversionRequestFee.Amount) >= potentialConversionTransaction.Amount)
                    {
                        this.logger.LogWarning("Conversion transaction '{0}' is no longer large enough to cover the fee.", potentialConversionTransaction.Id);
                        continue;
                    }

                    // We insert the fee distribution as a deposit to be processed, albeit with a special address.
                    // Deposits with this address as their destination will be distributed between the multisig members.
                    // Note that it will be actioned immediately as a matured deposit.
                    this.logger.LogInformation("Adding conversion fee distribution for transaction '{0}' to deposit list.", potentialConversionTransaction.Id);

                    // Instead of being a conversion deposit, the fee distribution is translated to its non-conversion equivalent.
                    DepositRetrievalType depositType = DepositRetrievalType.Small;

                    switch (potentialConversionTransaction.RetrievalType)
                    {
                    case DepositRetrievalType.ConversionSmall:
                        depositType = DepositRetrievalType.Small;
                        break;

                    case DepositRetrievalType.ConversionNormal:
                        depositType = DepositRetrievalType.Normal;
                        break;

                    case DepositRetrievalType.ConversionLarge:
                        depositType = DepositRetrievalType.Large;
                        break;
                    }

                    tempDepositList.Add(new Deposit(potentialConversionTransaction.Id,
                                                    depositType,
                                                    Money.Satoshis(interopConversionRequestFee.Amount),
                                                    this.network.ConversionTransactionFeeDistributionDummyAddress,
                                                    potentialConversionTransaction.TargetChain,
                                                    potentialConversionTransaction.BlockNumber,
                                                    potentialConversionTransaction.BlockHash));

                    if (!conversionExists)
                    {
                        this.logger.LogDebug("Adding conversion request for transaction '{0}' to repository.", potentialConversionTransaction.Id);

                        this.conversionRequestRepository.Save(new ConversionRequest()
                        {
                            RequestId     = potentialConversionTransaction.Id.ToString(),
                            RequestType   = ConversionRequestType.Mint,
                            Processed     = false,
                            RequestStatus = ConversionRequestStatus.Unprocessed,
                            // We do NOT convert to wei here yet. That is done when the minting transaction is submitted on the Ethereum network.
                            Amount             = (ulong)(potentialConversionTransaction.Amount - Money.Satoshis(interopConversionRequestFee.Amount)).Satoshi,
                            BlockHeight        = applicableHeader.Height,
                            DestinationAddress = potentialConversionTransaction.TargetAddress,
                            DestinationChain   = potentialConversionTransaction.TargetChain
                        });
                    }
                }

                maturedBlockDeposit.Deposits = tempDepositList.AsReadOnly();

                // Order all non-conversion deposit transactions in the block deterministically.
                maturedBlockDeposit.Deposits = maturedBlockDeposit.Deposits.OrderBy(x => x.Id, Comparer <uint256> .Create(DeterministicCoinOrdering.CompareUint256)).ToList();

                foreach (IDeposit deposit in maturedBlockDeposit.Deposits)
                {
                    this.logger.LogDebug("Deposit matured: {0}", deposit.ToString());
                }
            }

            // If we received a portion of blocks we can ask for a new portion without any delay.
            RecordLatestMatureDepositsResult result = await this.crossChainTransferStore.RecordLatestMatureDepositsAsync(matureBlockDeposits.Value).ConfigureAwait(false);

            return(!result.MatureDepositRecorded);
        }
Exemplo n.º 15
0
        private async Task <bool> ProcessMatureBlockDepositsAsync(SerializableResult <List <MaturedBlockDepositsModel> > matureBlockDeposits)
        {
            // "Value"'s count will be 0 if we are using NewtonSoft's serializer, null if using .Net Core 3's serializer.
            if (matureBlockDeposits.Value.Count == 0)
            {
                this.logger.Debug("Considering ourselves fully synced since no blocks were received.");

                // If we've received nothing we assume we are at the tip and should flush.
                // Same mechanic as with syncing headers protocol.
                await this.crossChainTransferStore.SaveCurrentTipAsync().ConfigureAwait(false);

                return(true);
            }

            // Filter out conversion transactions & also log what we've received for diagnostic purposes.
            foreach (MaturedBlockDepositsModel maturedBlockDeposit in matureBlockDeposits.Value)
            {
                foreach (IDeposit conversionTransaction in maturedBlockDeposit.Deposits.Where(d =>
                                                                                              d.RetrievalType == DepositRetrievalType.ConversionSmall ||
                                                                                              d.RetrievalType == DepositRetrievalType.ConversionNormal ||
                                                                                              d.RetrievalType == DepositRetrievalType.ConversionLarge))
                {
                    this.logger.Info("Conversion mint transaction " + conversionTransaction + " received in matured blocks.");

                    if (this.conversionRequestRepository.Get(conversionTransaction.Id.ToString()) != null)
                    {
                        this.logger.Info("Conversion mint transaction " + conversionTransaction + " already exists, ignoring.");

                        continue;
                    }

                    // 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.
                    ChainedHeader header = this.chainIndexer.Tip;
                    bool          found  = false;

                    while (true)
                    {
                        if (header == this.chainIndexer.Genesis)
                        {
                            break;
                        }

                        if (header.Previous.Header.Time <= maturedBlockDeposit.BlockInfo.BlockTime)
                        {
                            found = true;

                            break;
                        }

                        header = header.Previous;
                    }

                    if (!found)
                    {
                        continue;
                    }

                    this.conversionRequestRepository.Save(new ConversionRequest()
                    {
                        RequestId     = conversionTransaction.Id.ToString(),
                        RequestType   = ConversionRequestType.Mint,
                        Processed     = false,
                        RequestStatus = ConversionRequestStatus.Unprocessed,
                        // We do NOT convert to wei here yet. That is done when the minting transaction is submitted on the Ethereum network.
                        Amount             = (ulong)conversionTransaction.Amount.Satoshi,
                        BlockHeight        = header.Height,
                        DestinationAddress = conversionTransaction.TargetAddress,
                        DestinationChain   = conversionTransaction.TargetChain
                    });
                }

                // Order all other transactions in the block deterministically.
                maturedBlockDeposit.Deposits = maturedBlockDeposit.Deposits.Where(d =>
                                                                                  d.RetrievalType != DepositRetrievalType.ConversionSmall &&
                                                                                  d.RetrievalType != DepositRetrievalType.ConversionNormal &&
                                                                                  d.RetrievalType != DepositRetrievalType.ConversionLarge).OrderBy(x => x.Id, Comparer <uint256> .Create(DeterministicCoinOrdering.CompareUint256)).ToList();

                foreach (IDeposit deposit in maturedBlockDeposit.Deposits)
                {
                    this.logger.Trace(deposit.ToString());
                }
            }

            // If we received a portion of blocks we can ask for a new portion without any delay.
            RecordLatestMatureDepositsResult result = await this.crossChainTransferStore.RecordLatestMatureDepositsAsync(matureBlockDeposits.Value).ConfigureAwait(false);

            return(!result.MatureDepositRecorded);
        }
        /// <summary>Asks for blocks from another gateway node and then processes them.</summary>
        /// <returns><c>true</c> if delay between next time we should ask for blocks is required; <c>false</c> otherwise.</returns>
        /// <exception cref="OperationCanceledException">Thrown when <paramref name="cancellationToken"/> is cancelled.</exception>
        protected async Task <bool> SyncBatchOfBlocksAsync(CancellationToken cancellationToken = default(CancellationToken))
        {
            int blocksToRequest = 1;

            // TODO why are we asking for max of 1 block and if it's not suspended then 1000? investigate this logic in maturedBlocksProvider
            if (!this.store.HasSuspended())
            {
                blocksToRequest = MaxBlocksToRequest;
            }

            // API method that provides blocks should't give us blocks that are not mature!
            var model = new MaturedBlockRequestModel(this.store.NextMatureDepositHeight, blocksToRequest, MaxDepositsToRequest);

            this.logger.LogDebug("Request model created: {0}:{1}, {2}:{3}.", nameof(model.BlockHeight), model.BlockHeight, nameof(model.MaxBlocksToSend), model.MaxBlocksToSend);

            // Ask for blocks.
            SerializableResult <List <MaturedBlockDepositsModel> > matureBlockDepositsResult = await this.federationGatewayClient.GetMaturedBlockDepositsAsync(model, cancellationToken).ConfigureAwait(false);

            if (matureBlockDepositsResult == null)
            {
                this.logger.LogDebug("Failed to fetch matured block deposits from counter chain node; {0} didn't respond.", this.federationGatewayClient.EndpointUrl);
                this.logger.LogTrace("(-)[COUNTERCHAIN_NODE_NO_RESPONSE]:true");
                return(true);
            }

            if (matureBlockDepositsResult.Value == null)
            {
                this.logger.LogDebug("Failed to fetch matured block deposits from counter chain node; {0} didn't reply with any deposits; Message: {1}", this.federationGatewayClient.EndpointUrl, matureBlockDepositsResult.Message ?? "none");
                this.logger.LogTrace("(-)[COUNTERCHAIN_NODE_SENT_NO_DEPOSITS]:true");
                return(true);
            }

            bool delayRequired = true;

            // Log what we've received.
            foreach (MaturedBlockDepositsModel maturedBlockDeposit in matureBlockDepositsResult.Value)
            {
                // Order transactions in block deterministically
                maturedBlockDeposit.Deposits = maturedBlockDeposit.Deposits.OrderBy(x => x.Id, Comparer <uint256> .Create(DeterministicCoinOrdering.CompareUint256)).ToList();

                foreach (IDeposit deposit in maturedBlockDeposit.Deposits)
                {
                    this.logger.LogDebug("New deposit received BlockNumber={0}, TargetAddress='{1}', depositId='{2}', Amount='{3}'.", deposit.BlockNumber, deposit.TargetAddress, deposit.Id, deposit.Amount);
                }
            }

            if (matureBlockDepositsResult.Value.Count > 0)
            {
                RecordLatestMatureDepositsResult result = await this.store.RecordLatestMatureDepositsAsync(matureBlockDepositsResult.Value).ConfigureAwait(false);

                // If we received a portion of blocks we can ask for new portion without any delay.
                if (result.MatureDepositRecorded)
                {
                    delayRequired = false;
                }
            }
            else
            {
                this.logger.LogDebug("Considering ourselves fully synced since no blocks were received.");

                // If we've received nothing we assume we are at the tip and should flush.
                // Same mechanic as with syncing headers protocol.
                await this.store.SaveCurrentTipAsync().ConfigureAwait(false);
            }


            return(delayRequired);
        }