示例#1
0
        private Task GenerateBtcOutputs()
        {
            return(Retry.Try(async() =>
            {
                var setting = await GetAssetSetting("BTC");
                var hotWallet = OpenAssetsHelper.ParseAddress(setting.HotWallet);

                var outputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(setting.HotWallet, 0, false)).Cast <Coin>().ToList();

                var balance = new Money(outputs.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero));
                var outputSize = new Money(setting.OutputSize, MoneyUnit.BTC);

                Money changeBalance = Money.Zero;
                if (setting.ChangeWallet != setting.HotWallet && !string.IsNullOrEmpty(setting.ChangeWallet))
                {
                    var changeOutputs = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(setting.ChangeWallet, 0, false)).Cast <Coin>().ToList();
                    changeBalance = new Money(changeOutputs.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero));
                }

                if ((balance + changeBalance).ToDecimal(MoneyUnit.BTC) < setting.MinBalance)
                {
                    await SendBalanceNotifications("BTC", setting.HotWallet, setting.MinBalance);
                }


                var existingCoinsCount = outputs.Count(o => o.Amount <= outputSize && o.Amount > outputSize / 2);

                if (existingCoinsCount > setting.MinOutputsCount)
                {
                    return;
                }

                var generateCnt = setting.MaxOutputsCount - existingCoinsCount;

                var coins = outputs.Where(o => o.Amount > outputSize * 2).ToList();

                balance = coins.DefaultIfEmpty().Sum(o => o?.Amount ?? Money.Zero);

                generateCnt = Math.Min(generateCnt, (int)(balance / outputSize));
                if (generateCnt == 0)
                {
                    return;
                }
                await GenerateOutputs(generateCnt, coins, hotWallet, outputSize, await _assetRepostory.GetItemAsync("BTC"), setting);
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _logger));
        }
        public Task <CreateTransactionResponse> GetTransferAllTransaction(BitcoinAddress @from, BitcoinAddress to, Guid transactionId)
        {
            return(Retry.Try(async() =>
            {
                var context = new TransactionBuildContext(_connectionParams.Network, _pregeneratedOutputsQueueFactory);

                return await context.Build(async() =>
                {
                    var builder = new TransactionBuilder();
                    var uncoloredCoins = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(from.ToString())).ToList();
                    var coloredCoins = (await _bitcoinOutputsService.GetColoredUnspentOutputs(from.ToString())).ToList();

                    if (uncoloredCoins.Count == 0 && coloredCoins.Count == 0)
                    {
                        throw new BackendException("Address has no unspent outputs", ErrorCode.NoCoinsFound);
                    }

                    if (uncoloredCoins.Count > 0)
                    {
                        _transactionBuildHelper.SendWithChange(builder, context, uncoloredCoins, to, uncoloredCoins.Sum(o => o.TxOut.Value), from);
                    }
                    foreach (var assetGroup in coloredCoins.GroupBy(o => o.AssetId))
                    {
                        var sum = new AssetMoney(assetGroup.Key);
                        foreach (var coloredCoin in assetGroup)
                        {
                            sum += coloredCoin.Amount;
                        }

                        _transactionBuildHelper.SendAssetWithChange(builder, context, assetGroup.ToList(), to, sum, from);
                    }
                    await _transactionBuildHelper.AddFee(builder, context);

                    var buildedTransaction = builder.BuildTransaction(true);

                    await SaveSpentOutputs(transactionId, buildedTransaction);

                    await _signRequestRepository.InsertTransactionId(transactionId);

                    await SaveNewOutputs(transactionId, buildedTransaction, context);

                    return new CreateTransactionResponse(buildedTransaction.ToHex(), transactionId);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
示例#3
0
        public async Task <PrivateTransferResponse> GetPrivateTransferTransaction(BitcoinAddress source, BitcoinAddress destinationAddress, decimal amount, decimal fee, Guid transactionId)
        {
            return(await Retry.Try(() =>
            {
                var context = _transactionBuildContextFactory.Create(_connectionParams.Network);

                return context.Build(async() =>
                {
                    var coins = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(source.ToString())).OfType <Coin>().ToList();
                    var totalAmount = new Money(coins.Select(o => o.Amount).DefaultIfEmpty().Sum(o => o?.Satoshi ?? 0));

                    if (totalAmount.ToDecimal(MoneyUnit.BTC) < amount)
                    {
                        throw new BackendException($"The sum of total applicable outputs is less than the required: {amount} btc.", ErrorCode.NotEnoughBitcoinAvailable);
                    }

                    var builder = new TransactionBuilder();
                    builder.AddCoins(coins);
                    builder.SetChange(source);
                    builder.Send(destinationAddress, new Money(amount, MoneyUnit.BTC));
                    builder.SubtractFees();
                    if (fee == 0)
                    {
                        fee = builder.EstimateFees(await _feeProvider.GetFeeRate()).ToDecimal(MoneyUnit.BTC);
                    }
                    builder.SendFees(new Money(fee, MoneyUnit.BTC));

                    var tx = builder.BuildTransaction(true);

                    return new PrivateTransferResponse(tx.ToHex(), transactionId, fee);
                });
            }, exception => (exception as BackendException)?.Code == ErrorCode.TransactionConcurrentInputsProblem, 3, _log));
        }
        public async Task Send()
        {
            var feePerByte = (await _feeProvider.GetFeeRate()).FeePerK.Satoshi * _baseSettings.SpendChangeFeeRateMultiplier / 1000;

            var coins = (await _bitcoinOutputsService.GetUncoloredUnspentOutputs(_baseSettings.ChangeAddress, ConfirmationsCount)).OfType <Coin>()
                        .Where(o => o.Amount.Satoshi > feePerByte * Constants.InputSize).ToList();

            Utils.Shuffle(coins, new Random());

            while (coins.Count > _baseSettings.NumberOfChangeInputsForTransaction)
            {
                var part    = coins.Take(_baseSettings.NumberOfChangeInputsForTransaction).ToList();
                var balance = part.Sum(o => o.Amount);

                var builder = new TransactionBuilder();
                builder.AddCoins(part);
                builder.Send(new BitcoinPubKeyAddress(_baseSettings.HotWalletForPregeneratedOutputs), balance);

                var tr  = builder.BuildTransaction(false);
                var fee = new Money((await _feeProvider.CalcFeeForTransaction(builder)).Satoshi * _baseSettings.SpendChangeFeeRateMultiplier, MoneyUnit.Satoshi);
                if (fee > balance)
                {
                    await _log.WriteWarningAsync("SendFromChangeToHotWalletFunction", "Send", null,
                                                 $"Calculated fee is more than balance ({fee.Satoshi}>{balance})");

                    continue;
                }
                tr.Outputs[0].Value = tr.Outputs[0].Value - fee;

                var signed = await _signatureApiProvider.SignTransaction(tr.ToHex());

                var signedTr = new Transaction(signed);

                var transactionId = Guid.NewGuid();
                await _bitcoinBroadcastService.BroadcastTransaction(transactionId, signedTr);

                await _spentOutputService.SaveSpentOutputs(transactionId, signedTr);

                coins = coins.Skip(_baseSettings.NumberOfChangeInputsForTransaction).ToList();
            }
        }
        private async Task GenerateFeeOutputs()
        {
            await _logger.WriteInfoAsync("GenerateOutputsFunction", "GenerateFeeOutputs", null, "Start process");

            var queue = _pregeneratedOutputsQueueFactory.CreateFeeQueue();

            try
            {
                while (await queue.Count() < _baseSettings.MinPregeneratedOutputsCount)
                {
                    var uncoloredOutputs = await _bitcoinOutputsService.GetUncoloredUnspentOutputs(_baseSettings.HotWalletForPregeneratedOutputs);

                    var outputs = uncoloredOutputs.ToList();

                    var totalRequiredAmount = Money.FromUnit(_baseSettings.GenerateOutputsBatchSize * _baseSettings.PregeneratedFeeAmount,
                                                             MoneyUnit.BTC); // Convert to satoshi

                    var feeAmount = new Money(_baseSettings.PregeneratedFeeAmount, MoneyUnit.BTC);

                    if (outputs.Sum(o => o.TxOut.Value) < totalRequiredAmount)
                    {
                        throw new BackendException($"The sum of total applicable outputs is less than the required: {totalRequiredAmount} satoshis.",
                                                   ErrorCode.NotEnoughBitcoinAvailable);
                    }

                    var hotWallet = new BitcoinPubKeyAddress(_baseSettings.HotWalletForPregeneratedOutputs, _connectionParams.Network);
                    var builder   = new TransactionBuilder();

                    builder.AddCoins(outputs);
                    builder.SetChange(hotWallet);

                    for (var i = 0; i < _baseSettings.GenerateOutputsBatchSize; i++)
                    {
                        builder.Send(new BitcoinPubKeyAddress(_baseSettings.FeeAddress, _connectionParams.Network), feeAmount);
                    }

                    builder.SendFees(await _feeProvider.CalcFeeForTransaction(builder));

                    var signedHex = await _signatureApiProvider.SignTransaction(builder.BuildTransaction(false).ToHex());

                    var signedTr = new Transaction(signedHex);

                    var transactionId = Guid.NewGuid();

                    await _bitcoinClient.BroadcastTransaction(signedTr, transactionId);

                    await queue.EnqueueOutputs(signedTr.Outputs.AsCoins().Where(o => o.TxOut.Value == feeAmount).ToArray());

                    await FinishOutputs(transactionId, signedTr, hotWallet);
                }
            }
            finally
            {
                if (await queue.Count() < _baseSettings.MinPregeneratedOutputsCount && (DateTime.UtcNow - _lastWarningFeeSentTime).TotalHours > 1)
                {
                    var message = $"Count of fee outputs is less than {_baseSettings.MinPregeneratedOutputsCount}";
                    await _slackNotifier.FinanceWarningAsync(message);

                    await _emailNotifier.WarningAsync("Bitcoin job", message);

                    _lastWarningFeeSentTime = DateTime.UtcNow;
                }
                await _logger.WriteInfoAsync("GenerateOutputsFunction", "GenerateFeeOutputs", null, "End process");
            }
        }