/// <inheritdoc />
        public VerboseAddressBalancesResult GetVerboseAddressBalancesData(string[] addresses)
        {
            (bool isQueryable, string reason) = this.IsQueryable();

            if (!isQueryable)
            {
                return(VerboseAddressBalancesResult.RequestFailed(reason));
            }

            var result = new VerboseAddressBalancesResult();

            lock (this.lockObject)
            {
                foreach (var address in addresses)
                {
                    AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address);

                    var copy = new AddressIndexerData()
                    {
                        Address        = indexData.Address,
                        BalanceChanges = new List <AddressBalanceChange>(indexData.BalanceChanges)
                    };

                    result.BalancesData.Add(copy);
                }
            }

            result.ConsensusTipHeight = this.consensusManager.Tip.Height;

            return(result);
        }
Example #2
0
        /// <inheritdoc />
        public VerboseAddressBalancesResult GetAddressIndexerState(string[] addresses)
        {
            var result = new VerboseAddressBalancesResult(this.consensusManager.Tip.Height);

            if (addresses.Length == 0)
            {
                return(result);
            }

            var(isQueryable, reason) = IsQueryable();

            if (!isQueryable)
            {
                return(VerboseAddressBalancesResult.RequestFailed(reason));
            }

            lock (this.lockObject)
            {
                foreach (var address in addresses)
                {
                    var indexData = this.addressIndexRepository.GetOrCreateAddress(address);

                    var copy = new AddressIndexerData
                    {
                        Address        = indexData.Address,
                        BalanceChanges = new List <AddressBalanceChange>(indexData.BalanceChanges)
                    };

                    result.BalancesData.Add(copy);
                }
            }

            return(result);
        }
        private async Task UpdateCollateralInfoAsync(CancellationToken cancellation)
        {
            List <string> addressesToCheck;

            lock (this.locker)
            {
                addressesToCheck = this.balancesDataByAddress.Keys.ToList();
            }

            if (addressesToCheck.Count == 0)
            {
                this.collateralUpdated = true;

                this.logger.LogInformation("None of the federation members has a collateral requirement configured.");
                this.logger.LogTrace("(-)[NOTHING_TO_CHECK]:true");
                return;
            }

            this.logger.LogDebug("Addresses to check {0}.", addressesToCheck.Count);

            VerboseAddressBalancesResult verboseAddressBalanceResult = await this.blockStoreClient.GetVerboseAddressesBalancesDataAsync(addressesToCheck, cancellation).ConfigureAwait(false);

            if (verboseAddressBalanceResult == null)
            {
                this.logger.LogWarning("Failed to update collateral, please ensure that the mainnet gateway node is running and it's API feature is enabled.");
                this.logger.LogTrace("(-)[CALL_RETURNED_NULL_RESULT]:false");
                return;
            }

            if (!string.IsNullOrEmpty(verboseAddressBalanceResult.Reason))
            {
                this.logger.LogWarning("Failed to fetch address balances from the counter chain node : {0}", verboseAddressBalanceResult.Reason);
                this.logger.LogTrace("(-)[FAILED]:{0}", verboseAddressBalanceResult.Reason);
                return;
            }

            this.logger.LogDebug("Addresses received {0}.", verboseAddressBalanceResult.BalancesData.Count);

            if (verboseAddressBalanceResult.BalancesData.Count != addressesToCheck.Count)
            {
                this.logger.LogDebug("Expected {0} data entries but received {1}.", addressesToCheck.Count, verboseAddressBalanceResult.BalancesData.Count);
                this.logger.LogTrace("(-)[CALL_RETURNED_INCONSISTENT_DATA]:false");
                return;
            }

            lock (this.locker)
            {
                foreach (AddressIndexerData balanceData in verboseAddressBalanceResult.BalancesData)
                {
                    this.logger.LogDebug("Updating federation member address {0}.", balanceData.Address);
                    this.balancesDataByAddress[balanceData.Address] = balanceData;
                }

                this.counterChainConsensusTipHeight = verboseAddressBalanceResult.ConsensusTipHeight;
            }

            this.collateralUpdated = true;
        }
        public IActionResult GetVerboseAddressesBalancesData(string addresses)
        {
            try
            {
                string[] addressesArray = addresses?.Split(',') ?? new string[] { };

                this.logger.LogDebug("Asking data for {0} addresses.", addressesArray.Length);

                VerboseAddressBalancesResult result = this.addressIndexer.GetAddressIndexerState(addressesArray);

                return(this.Json(result));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        /// <inheritdoc />
        public VerboseAddressBalancesResult GetAddressIndexerState(string[] addresses)
        {
            // If the containing feature is not initialized then wait a bit.
            this.InitializingFeature?.WaitInitialized();

            var result = new VerboseAddressBalancesResult(this.consensusManager.Tip.Height);

            if (addresses.Length == 0)
            {
                return(result);
            }

            if (!this.storeSettings.AddressIndex)
            {
                throw new NotSupportedException("Address indexing is not enabled.");
            }

            (bool isQueryable, string reason) = this.IsQueryable();

            if (!isQueryable)
            {
                return(VerboseAddressBalancesResult.RequestFailed(reason));
            }

            lock (this.lockObject)
            {
                foreach (var address in addresses)
                {
                    AddressIndexerData indexData = this.addressIndexRepository.GetOrCreateAddress(address);

                    var copy = new AddressIndexerData()
                    {
                        Address        = indexData.Address,
                        BalanceChanges = new List <AddressBalanceChange>(indexData.BalanceChanges)
                    };

                    result.BalancesData.Add(copy);
                }
            }

            return(result);
        }
        public async Task CanInitializeAndCheckCollateralAsync()
        {
            var blockStoreClientMock = new Mock <IBlockStoreClient>();

            var collateralData = new VerboseAddressBalancesResult(this.collateralCheckHeight + 1000)
            {
                BalancesData = new List <AddressIndexerData>()
                {
                    new AddressIndexerData()
                    {
                        Address        = this.collateralFederationMembers[0].CollateralMainchainAddress,
                        BalanceChanges = new List <AddressBalanceChange>()
                        {
                            new AddressBalanceChange()
                            {
                                BalanceChangedHeight = 0, Deposited = true, Satoshi = this.collateralFederationMembers[0].CollateralAmount
                            }
                        }
                    },
                    new AddressIndexerData()
                    {
                        Address        = this.collateralFederationMembers[1].CollateralMainchainAddress,
                        BalanceChanges = new List <AddressBalanceChange>()
                        {
                            new AddressBalanceChange()
                            {
                                BalanceChangedHeight = 0, Deposited = true, Satoshi = this.collateralFederationMembers[1].CollateralAmount + 10
                            }
                        }
                    },
                    new AddressIndexerData()
                    {
                        Address        = this.collateralFederationMembers[2].CollateralMainchainAddress,
                        BalanceChanges = new List <AddressBalanceChange>()
                        {
                            new AddressBalanceChange()
                            {
                                BalanceChangedHeight = 0, Deposited = true, Satoshi = this.collateralFederationMembers[2].CollateralAmount - 10
                            }
                        }
                    }
                }
            };

            blockStoreClientMock.Setup(x => x.GetVerboseAddressesBalancesDataAsync(It.IsAny <IEnumerable <string> >(), It.IsAny <CancellationToken>())).ReturnsAsync(collateralData);

            this.collateralChecker.SetPrivateVariableValue("blockStoreClient", blockStoreClientMock.Object);

            await this.collateralChecker.InitializeAsync();

            Assert.True(this.collateralChecker.CheckCollateral(this.collateralFederationMembers[0], this.collateralCheckHeight));
            Assert.True(this.collateralChecker.CheckCollateral(this.collateralFederationMembers[1], this.collateralCheckHeight));
            Assert.False(this.collateralChecker.CheckCollateral(this.collateralFederationMembers[2], this.collateralCheckHeight));

            // Now change what the client returns and make sure collateral check fails after update.
            AddressIndexerData updated = collateralData.BalancesData.First(b => b.Address == this.collateralFederationMembers[0].CollateralMainchainAddress);

            updated.BalanceChanges.First().Satoshi = this.collateralFederationMembers[0].CollateralAmount - 1;

            // Wait CollateralUpdateIntervalSeconds + 1 seconds

            await Task.Delay(21_000);

            Assert.False(this.collateralChecker.CheckCollateral(this.collateralFederationMembers[0], this.collateralCheckHeight));

            this.collateralChecker.Dispose();
        }
        public GetUTXOsResponseModel GetUTXOsForAddress([FromQuery] string address)
        {
            VerboseAddressBalancesResult balancesResult = this.addressIndexer.GetAddressIndexerState(new[] { address });

            if (balancesResult.BalancesData == null || balancesResult.BalancesData.Count != 1)
            {
                this.logger.LogWarning("No balances found for address {0}, Reason: {1}", address, balancesResult.Reason);
                return(new GetUTXOsResponseModel()
                {
                    Reason = balancesResult.Reason
                });
            }

            BitcoinAddress bitcoinAddress = this.network.CreateBitcoinAddress(address);

            AddressIndexerData addressBalances = balancesResult.BalancesData.First();

            List <AddressBalanceChange> deposits = addressBalances.BalanceChanges.Where(x => x.Deposited).ToList();
            long totalDeposited = deposits.Sum(x => x.Satoshi);
            long totalWithdrawn = addressBalances.BalanceChanges.Where(x => !x.Deposited).Sum(x => x.Satoshi);

            long balanceSat = totalDeposited - totalWithdrawn;

            List <int>        heights         = deposits.Select(x => x.BalanceChangedHeight).Distinct().ToList();
            HashSet <uint256> blocksToRequest = new HashSet <uint256>(heights.Count);

            foreach (int height in heights)
            {
                uint256 blockHash = this.chainState.ConsensusTip.GetAncestor(height).Header.GetHash();
                blocksToRequest.Add(blockHash);
            }

            List <Block>    blocks             = this.blockStore.GetBlocks(blocksToRequest.ToList());
            List <OutPoint> collectedOutPoints = new List <OutPoint>(deposits.Count);

            foreach (List <Transaction> txList in blocks.Select(x => x.Transactions))
            {
                foreach (Transaction transaction in txList.Where(x => !x.IsCoinBase && !x.IsCoinStake))
                {
                    for (int i = 0; i < transaction.Outputs.Count; i++)
                    {
                        if (!transaction.Outputs[i].IsTo(bitcoinAddress))
                        {
                            continue;
                        }

                        collectedOutPoints.Add(new OutPoint(transaction, i));
                    }
                }
            }

            FetchCoinsResponse fetchCoinsResponse = this.coinView.FetchCoins(collectedOutPoints.ToArray());

            GetUTXOsResponseModel response = new GetUTXOsResponseModel()
            {
                BalanceSat = balanceSat,
                UTXOs      = new List <UTXOModel>()
            };

            foreach (KeyValuePair <OutPoint, UnspentOutput> unspentOutput in fetchCoinsResponse.UnspentOutputs)
            {
                if (unspentOutput.Value.Coins == null)
                {
                    continue; // spent
                }
                OutPoint outPoint = unspentOutput.Key;
                Money    value    = unspentOutput.Value.Coins.TxOut.Value;

                response.UTXOs.Add(new UTXOModel(outPoint, value));
            }

            return(response);
        }