Exemple #1
0
        public IBitcoinBasedTransaction SignP2PkSwapRefundTx(BitcoinBasedCurrency currency)
        {
            const int paymentQty = 1_0000_0000;

            var paymentTx        = CreateP2PkSwapPaymentTx(currency);
            var paymentTxOutputs = paymentTx.Outputs.Where(o => o.Value == paymentQty).ToArray();

            var       lockTime = DateTimeOffset.Now.AddHours(12);
            const int amount   = 9999_0000;
            const int fee      = 1_0000;
            // change = 0;

            var refundTx = BitcoinBasedCommon.CreatePaymentTx(
                currency: Common.BtcTestNet,
                outputs: paymentTxOutputs,
                from: Common.Alice.PubKey,
                to: Common.Alice.PubKey,
                amount: amount,
                fee: fee,
                lockTime: lockTime
                );

            var sigHash = new uint256(refundTx.GetSignatureHash(paymentTxOutputs.First()));

            var aliceSign    = Common.Alice.Sign(sigHash, SigHash.All);
            var bobSign      = Common.Bob.Sign(sigHash, SigHash.All);
            var refundScript = BitcoinBasedSwapTemplate.GenerateSwapRefund(aliceSign, bobSign);

            refundTx.NonStandardSign(refundScript, paymentTxOutputs.First());

            Assert.True(refundTx.Verify(paymentTxOutputs));

            return(refundTx);
        }
Exemple #2
0
        public IBitcoinBasedTransaction SignHtlcP2PkhScriptSwapRedeemTx(BitcoinBasedCurrency currency)
        {
            const int paymentQty = 1_0000_0000;

            var(paymentTx, redeemScript) = CreateHtlcP2PkhScriptSwapPaymentTx(currency);
            var paymentTxOutputs = paymentTx.Outputs
                                   .Where(o => o.Value == paymentQty)
                                   .ToArray();

            const int amount = 9999_0000;
            const int fee    = 1_0000;

            var redeemTx = currency.CreatePaymentTx(
                unspentOutputs: paymentTxOutputs,
                destinationAddress: Common.Bob.PubKey.GetAddress(currency),
                changeAddress: Common.Bob.PubKey.GetAddress(currency),
                amount: amount,
                fee: fee,
                lockTime: DateTimeOffset.MinValue);

            var sigHash = new uint256(redeemTx.GetSignatureHash(new Script(redeemScript), paymentTxOutputs.First()));

            var scriptSig = BitcoinBasedSwapTemplate.GenerateP2PkhSwapRedeemForP2Sh(
                sig: Common.Bob.Sign(sigHash, SigHash.All).ToBytes(),
                pubKey: Common.Bob.PubKey.ToBytes(),
                secret: Common.Secret,
                redeemScript: redeemScript);

            redeemTx.NonStandardSign(scriptSig, paymentTxOutputs.First());

            Assert.True(redeemTx.Verify(paymentTxOutputs));

            return(redeemTx);
        }
        public virtual IBitcoinBasedTransaction CreateHtlcP2PkhSwapPaymentTx(
            IEnumerable <ITxOutput> unspentOutputs,
            string aliceRefundAddress,
            string bobAddress,
            DateTimeOffset lockTime,
            byte[] secretHash,
            int secretSize,
            long amount,
            long fee)
        {
            var coins = unspentOutputs
                        .Cast <BitcoinBasedTxOutput>()
                        .Select(o => o.Coin);

            var swap = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: aliceRefundAddress,
                bobAddress: bobAddress,
                lockTimeStamp: lockTime.ToUnixTimeSeconds(),
                secretHash: secretHash,
                secretSize: secretSize,
                expectedNetwork: Network);

            var change = BitcoinAddress.Create(aliceRefundAddress, Network)
                         .ScriptPubKey;

            return(BitcoinBasedTransaction.CreateTransaction(
                       currency: this,
                       coins: coins,
                       destination: swap,
                       change: change,
                       amount: amount,
                       fee: fee));
        }
Exemple #4
0
        public IBitcoinBasedTransaction SignHtlcP2PkhSwapRedeemTxByBob(BitcoinBasedCurrency currency)
        {
            const int paymentQty = 3000;

            var paymentTx        = CreateHtlcP2PkhSwapPaymentTxAlice2Bob(currency);
            var paymentTxOutputs = paymentTx.Outputs
                                   .Where(o => o.Value == paymentQty)
                                   .ToArray();

            const int amount = 2000;
            const int fee    = 1000;

            var redeemTx = currency.CreatePaymentTx(
                unspentOutputs: paymentTxOutputs,
                destinationAddress: Common.Bob.PubKey.GetAddress(currency),
                changeAddress: Common.Bob.PubKey.GetAddress(currency),
                amount: amount,
                fee: fee);

            var sigHash      = new uint256(redeemTx.GetSignatureHash(paymentTxOutputs.First()));
            var bobSign      = Common.Bob.Sign(sigHash, SigHash.All).ToBytes();
            var bobPubKey    = Common.Bob.PubKey.ToBytes();
            var redeemScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapRedeem(bobSign, bobPubKey, Common.Secret);

            redeemTx.NonStandardSign(redeemScript, paymentTxOutputs.First());

            Assert.True(redeemTx.Verify(paymentTxOutputs));

            return(redeemTx);
        }
        public virtual IBitcoinBasedTransaction CreateP2PkhSwapPaymentTx(
            IEnumerable <ITxOutput> unspentOutputs,
            byte[] aliceRefundPubKey,
            byte[] bobRefundPubKey,
            string bobAddress,
            byte[] secretHash,
            long amount,
            long fee)
        {
            var coins = unspentOutputs
                        .Cast <BitcoinBasedTxOutput>()
                        .Select(o => o.Coin);

            var alicePubKey = new PubKey(aliceRefundPubKey);

            var swap = BitcoinBasedSwapTemplate.GenerateP2PkhSwapPayment(
                aliceRefundPubKey: aliceRefundPubKey,
                bobRefundPubKey: bobRefundPubKey,
                bobAddress: bobAddress,
                secretHash: secretHash,
                expectedNetwork: Network);

            return(BitcoinBasedTransaction.CreateTransaction(
                       currency: this,
                       coins: coins,
                       destination: swap,
                       change: alicePubKey.Hash.ScriptPubKey,
                       amount: amount,
                       fee: fee));
        }
        public async Task <IBitcoinBasedTransaction> SignRefundTxAsync(
            IBitcoinBasedTransaction refundTx,
            IBitcoinBasedTransaction paymentTx,
            WalletAddress refundAddress)
        {
            var tx = refundTx.Clone();

            if (tx.Inputs.Length != 1)
            {
                Log.Error("Refund transaction has zero or more than one input");
                return(null);
            }

            var spentOutput = paymentTx.Outputs.FirstOrDefault(o => o.IsSwapPayment);

            if (spentOutput == null)
            {
                Log.Error("Payment transaction hasn't swap output");
                return(null);
            }

            // firstly check, if transaction is already signed
            if (tx.Verify(spentOutput))
            {
                return(tx);
            }

            // clean any signature, if exists
            tx.NonStandardSign(Script.Empty, 0);

            var sigHash = tx.GetSignatureHash(spentOutput);

            var signature = await Account.Wallet
                            .SignHashAsync(
                hash : sigHash,
                address : refundAddress)
                            .ConfigureAwait(false);

            if (signature == null)
            {
                Log.Error("Refund transaction signature error");
                return(null);
            }

            var refundScript = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefund(
                aliceRefundSig: signature,
                aliceRefundPubKey: refundAddress.PublicKeyBytes());

            tx.NonStandardSign(refundScript, spentOutput);

            if (!tx.Verify(spentOutput, out var errors))
            {
                Log.Error("Refund transaction verify errors: {errors}", errors);
                return(null);
            }

            return(tx);
        }
Exemple #7
0
        public async Task <IBitcoinBasedTransaction> SignHtlcSwapRedeemForP2ShTxAsync(
            IBitcoinBasedTransaction redeemTx,
            IBitcoinBasedTransaction paymentTx,
            WalletAddress redeemAddress,
            byte[] secret,
            byte[] redeemScript)
        {
            var tx = redeemTx.Clone();

            var spentOutput = paymentTx.Outputs
                              .Cast <BitcoinBasedTxOutput>()
                              .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript));

            if (spentOutput == null)
            {
                Log.Error("Payment transaction hasn't pay to script hash output for redeem script");
                return(null);
            }

            var sigHash = tx.GetSignatureHash(new Script(redeemScript), spentOutput);

            var signature = await Account.Wallet
                            .SignHashAsync(
                hash : sigHash,
                address : redeemAddress,
                currency : Account.Currencies.GetByName(Account.Currency))
                            .ConfigureAwait(false);

            if (signature == null)
            {
                Log.Error("Redeem transaction signature error");
                return(null);
            }

            var redeemScriptSig = BitcoinBasedSwapTemplate.GenerateP2PkhSwapRedeemForP2Sh(
                sig: signature,
                pubKey: redeemAddress.PublicKeyBytes(),
                secret: secret,
                redeemScript: redeemScript);

            tx.NonStandardSign(redeemScriptSig, spentOutput);

            if (!tx.Verify(spentOutput, out var errors, Account.Config))
            {
                Log.Error("Redeem transaction verify errors: {errors}", errors);
                return(null);
            }

            return(tx);
        }
        public void ExtractSecretTest()
        {
            var redeemScript = new Script(SwapRedeemScript);

            Assert.NotNull(redeemScript);

            var secret = BitcoinBasedSwapTemplate.ExtractSecretFromP2PkhSwapRedeem(redeemScript);

            Assert.NotNull(secret);

            var secretHash = Convert.ToBase64String(CurrencySwap.CreateSwapSecretHash160(secret));

            Assert.Equal(SwapSecretHash, secretHash);
        }
        public async Task <IBitcoinBasedTransaction> SignRedeemTxAsync(
            IBitcoinBasedTransaction redeemTx,
            IBitcoinBasedTransaction paymentTx,
            WalletAddress redeemAddress,
            byte[] secret)
        {
            var tx = redeemTx.Clone();

            var spentOutput = paymentTx.Outputs.FirstOrDefault(o => o.IsSwapPayment);

            if (spentOutput == null)
            {
                Log.Error("Payment transaction hasn't swap output");
                return(null);
            }

            var sigHash = tx.GetSignatureHash(spentOutput);

            var signature = await Account.Wallet
                            .SignHashAsync(
                hash : sigHash,
                address : redeemAddress)
                            .ConfigureAwait(false);

            if (signature == null)
            {
                Log.Error("Redeem transaction signature error");
                return(null);
            }

            var redeemScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapRedeem(
                sig: signature,
                pubKey: redeemAddress.PublicKeyBytes(),
                secret: secret);

            tx.NonStandardSign(redeemScript, spentOutput);

            if (!tx.Verify(spentOutput, out var errors))
            {
                Log.Error("Redeem transaction verify errors: {errors}", errors);
                return(null);
            }

            return(tx);
        }
Exemple #10
0
        public IBitcoinBasedTransaction SignHtlcP2PkhScriptSwapRefundTx(BitcoinBasedConfig currency)
        {
            const int paymentQty = 1_0000_0000;

            var(paymentTx, redeemScriptBytes) = CreateHtlcP2PkhScriptSwapPaymentTx(currency);
            var paymentTxOutputs = paymentTx.Outputs.Where(o => o.Value == paymentQty).ToArray();

            var       lockTime = DateTimeOffset.UtcNow.AddHours(1);
            const int amount   = 9999_0000;
            const int fee      = 1_0000;
            // change = 0;

            var redeemScript = new Script(redeemScriptBytes);

            var refundTx = BitcoinBasedCommon.CreatePaymentTx(
                currency: Common.BtcTestNet,
                outputs: paymentTxOutputs,
                from: Common.Alice.PubKey,
                to: Common.Alice.PubKey,
                amount: amount,
                fee: fee,
                lockTime: lockTime,
                knownRedeems: redeemScript
                );

            var sigHash = new uint256(refundTx.GetSignatureHash(redeemScript, paymentTxOutputs.First()));

            var aliceSign = Common.Alice.Sign(sigHash, SigHash.All);

            var refundScript = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefundForP2Sh(
                aliceRefundSig: aliceSign.ToBytes(),
                aliceRefundPubKey: Common.Alice.PubKey.ToBytes(),
                redeemScript: redeemScriptBytes);

            refundTx.NonStandardSign(refundScript, paymentTxOutputs.First());

            Assert.True(refundTx.Verify(paymentTxOutputs, currency));

            return(refundTx);
        }
        public static Task StartSwapSpentControlAsync(
            Swap swap,
            CurrencyConfig currency,
            DateTime refundTimeUtc,
            TimeSpan interval,
            Func <Swap, ITxPoint, CancellationToken, Task> completionHandler = null,
            Func <Swap, CancellationToken, Task> refundTimeReachedHandler    = null,
            CancellationToken cancellationToken = default)
        {
            return(Task.Run(async() =>
            {
                try
                {
                    var bitcoinBased = (BitcoinBasedConfig)currency;

                    var side = swap.Symbol
                               .OrderSideForBuyCurrency(swap.PurchasedCurrency);

                    var requiredAmount = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier);
                    var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount);

                    var lockTimeInSeconds = swap.IsInitiator
                        ? CurrencySwap.DefaultInitiatorLockTimeInSeconds
                        : CurrencySwap.DefaultAcceptorLockTimeInSeconds;

                    var refundTimeUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds))
                                             .ToUnixTimeSeconds();

                    var redeemScript = swap.RefundAddress == null && swap.RedeemScript != null
                        ? new Script(Convert.FromBase64String(swap.RedeemScript))
                        : BitcoinBasedSwapTemplate
                                       .GenerateHtlcP2PkhSwapPayment(
                        aliceRefundAddress: swap.RefundAddress,
                        bobAddress: swap.PartyAddress,
                        lockTimeStamp: refundTimeUtcInSec,
                        secretHash: swap.SecretHash,
                        secretSize: CurrencySwap.DefaultSecretSize,
                        expectedNetwork: bitcoinBased.Network);

                    var swapOutput = ((IBitcoinBasedTransaction)swap.PaymentTx)
                                     .Outputs
                                     .Cast <BitcoinBasedTxOutput>()
                                     .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript) && o.Value >= requiredAmountInSatoshi);

                    if (swapOutput == null)
                    {
                        throw new InternalException(
                            code: Errors.SwapError,
                            description: "Payment tx have not swap output");
                    }

                    while (!cancellationToken.IsCancellationRequested)
                    {
                        Log.Debug("Output spent control for {@currency} swap {@swapId}", currency.Name, swap.Id);

                        var result = await currency
                                     .GetSpentPointAsync(
                            hash: swap.PaymentTxId,
                            index: swapOutput.Index,
                            cancellationToken: cancellationToken)
                                     .ConfigureAwait(false);

                        if (result != null && !result.HasError)
                        {
                            if (result.Value != null)
                            {
                                await completionHandler.Invoke(swap, result.Value, cancellationToken)
                                .ConfigureAwait(false);

                                break;
                            }
                        }

                        if (DateTime.UtcNow >= refundTimeUtc)
                        {
                            await refundTimeReachedHandler.Invoke(swap, cancellationToken)
                            .ConfigureAwait(false);

                            break;
                        }

                        await Task.Delay(interval, cancellationToken)
                        .ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException)
                {
                    Log.Debug("StartSwapSpentControlAsync canceled.");
                }
                catch (Exception e)
                {
                    Log.Error(e, "StartSwapSpentControlAsync error");
                }
            }, cancellationToken));
        }
Exemple #12
0
        public async Task <IBitcoinBasedTransaction> SignHtlcSwapRefundForP2ShTxAsync(
            IBitcoinBasedTransaction refundTx,
            IBitcoinBasedTransaction paymentTx,
            WalletAddress refundAddress,
            byte[] redeemScript)
        {
            var tx = refundTx.Clone();

            if (tx.Inputs.Length != 1)
            {
                Log.Error("Refund transaction has zero or more than one input");
                return(null);
            }

            var spentOutput = paymentTx.Outputs
                              .Cast <BitcoinBasedTxOutput>()
                              .FirstOrDefault(o => o.IsPayToScriptHash(redeemScript));

            if (spentOutput == null)
            {
                Log.Error("Payment transaction hasn't pay to script hash output for redeem script");
                return(null);
            }

            // firstly check, if transaction is already signed
            if (tx.Verify(spentOutput, Account.Config))
            {
                return(tx);
            }

            // clean any signature, if exists
            tx.NonStandardSign(Script.Empty, 0);

            var sigHash = tx.GetSignatureHash(new Script(redeemScript), spentOutput);

            var signature = await Account.Wallet
                            .SignHashAsync(
                hash : sigHash,
                address : refundAddress,
                currency : Account.Currencies.GetByName(Account.Currency))
                            .ConfigureAwait(false);

            if (signature == null)
            {
                Log.Error("Refund transaction signature error");
                return(null);
            }

            var refundScriptSig = BitcoinBasedSwapTemplate.GenerateHtlcSwapRefundForP2Sh(
                aliceRefundSig: signature,
                aliceRefundPubKey: refundAddress.PublicKeyBytes(),
                redeemScript: redeemScript);

            tx.NonStandardSign(refundScriptSig, spentOutput);

            if (!tx.Verify(spentOutput, out var errors, Account.Config))
            {
                Log.Error("Refund transaction verify errors: {errors}", errors);
                return(null);
            }

            return(tx);
        }
        public async override Task RefundAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRefundBroadcast))
            {
                _ = TrackTransactionConfirmationAsync(
                    swap: swap,
                    currency: Currencies.GetByName(swap.SoldCurrency),
                    dataRepository: _account.DataRepository,
                    txId: swap.RefundTx.Id,
                    confirmationHandler: RefundConfirmedEventHandler,
                    cancellationToken: cancellationToken);

                return;
            }

            var lockTimeInSeconds = swap.IsInitiator
                ? DefaultInitiatorLockTimeInSeconds
                : DefaultAcceptorLockTimeInSeconds;

            var lockTime = swap.TimeStamp.ToUniversalTime() + TimeSpan.FromSeconds(lockTimeInSeconds);

            await RefundTimeDelayAsync(lockTime, cancellationToken)
            .ConfigureAwait(false);

            var refundAddress = await _account
                                .GetAddressAsync(swap.RefundAddress)
                                .ConfigureAwait(false);

            var currency = Currencies.Get <BitcoinBasedConfig>(Currency);

            var redeemScript = BitcoinBasedSwapTemplate
                               .GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: refundAddress.Address,
                bobAddress: swap.PartyAddress,
                lockTimeStamp: lockTime.ToUnixTimeSeconds(),
                secretHash: swap.SecretHash,
                secretSize: DefaultSecretSize,
                expectedNetwork: currency.Network)
                               .ToBytes();

            swap.RefundTx = await CreateRefundTxAsync(
                swap : swap,
                paymentTx : (IBitcoinBasedTransaction)swap.PaymentTx,
                refundAddress : refundAddress.Address,
                lockTime : lockTime,
                redeemScript : redeemScript)
                            .ConfigureAwait(false);

            swap.RefundTx = await SignRefundTxAsync(
                swap : swap,
                refundTx : (IBitcoinBasedTransaction)swap.RefundTx,
                paymentTx : (IBitcoinBasedTransaction)swap.PaymentTx,
                refundAddress : refundAddress,
                redeemScript : redeemScript)
                            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRefundSigned;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRefundSigned, cancellationToken)
            .ConfigureAwait(false);

            _ = swap.RefundTx.ForceBroadcast(
                blockchainApi: Config.BlockchainApi,
                swap: swap,
                interval: ForceRefundInterval,
                completionHandler: RefundBroadcastEventHandler,
                cancellationToken: cancellationToken);
        }
        public override async Task RedeemAsync(
            Swap swap,
            CancellationToken cancellationToken = default)
        {
            Log.Debug("Redeem for swap {@swap}.", swap.Id);

            var currency = Currencies.GetByName(swap.PurchasedCurrency);

            var needReplaceTx = false;

            if (swap.StateFlags.HasFlag(SwapStateFlags.IsRedeemBroadcast))
            {
                Log.Debug("Check redeem confirmation for swap {@swap}.", swap.Id);

                // redeem already broadcast
                var result = await currency
                             .IsTransactionConfirmed(
                    txId : swap.RedeemTx.Id,
                    cancellationToken : cancellationToken)
                             .ConfigureAwait(false);

                if (result == null)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Result is null.");
                    return;
                }
                else if (result.HasError && result.Error.Code == (int)HttpStatusCode.NotFound)
                {
                    // probably the transaction was deleted by miners
                    Log.Debug("Probably the transaction {@tx} was deleted by miners.", swap.RedeemTx.Id);
                    needReplaceTx = true;
                }
                else if (result.HasError)
                {
                    Log.Error("Error while check bitcoin based redeem tx confirmation. Code: {@code}. Description: {@description}.",
                              result.Error.Code,
                              result.Error.Description);

                    return;
                }
                else if (result.Value.IsConfirmed) // tx already confirmed
                {
                    Log.Debug("Transaction {@tx} is already confirmed.", swap.RedeemTx.Id);

                    await RedeemConfirmedEventHandler(swap, result.Value.Transaction, cancellationToken)
                    .ConfigureAwait(false);

                    return;
                }

                var currentTimeUtc = DateTime.UtcNow;

                var creationTimeUtc = swap.RedeemTx.CreationTime != null
                    ? swap.RedeemTx.CreationTime.Value.ToUniversalTime()
                    : swap.TimeStamp.ToUniversalTime();

                var difference = currentTimeUtc - creationTimeUtc;

                Log.Debug("Currenct time: {@current}, creation time: {@now}, difference: {@diff}",
                          currentTimeUtc,
                          creationTimeUtc,
                          difference);

                // check transaction creation time and try replacing it with a higher fee
                if (difference >= TimeSpan.FromHours(4))
                {
                    needReplaceTx = true;
                }

                if (!needReplaceTx)
                {
                    _ = TrackTransactionConfirmationAsync(
                        swap: swap,
                        currency: currency,
                        dataRepository: _account.DataRepository,
                        txId: swap.RedeemTx.Id,
                        confirmationHandler: RedeemConfirmedEventHandler,
                        cancellationToken: cancellationToken);

                    return;
                }
            }

            if (swap.IsInitiator)
            {
                var redeemDeadline = swap.TimeStamp.ToUniversalTime().AddSeconds(DefaultAcceptorLockTimeInSeconds) - RedeemTimeReserve;

                if (DateTime.UtcNow > redeemDeadline)
                {
                    Log.Error("Redeem dedline reached for swap {@swap}", swap.Id);
                    return;
                }
            }

            var redeemAddress = await _account
                                .GetFreeInternalAddressAsync()
                                .ConfigureAwait(false);

            var lockTimeInSeconds = swap.IsInitiator
                ? DefaultAcceptorLockTimeInSeconds
                : DefaultInitiatorLockTimeInSeconds;

            var refundTimeUtcInSec = new DateTimeOffset(swap.TimeStamp.ToUniversalTime().AddSeconds(lockTimeInSeconds))
                                     .ToUnixTimeSeconds();

            var bitcoinBased = (BitcoinBasedConfig)currency;

            var partyRedeemScript = swap.PartyRefundAddress == null && swap.PartyRedeemScript != null
                ? new Script(Convert.FromBase64String(swap.PartyRedeemScript))
                : BitcoinBasedSwapTemplate
                                    .GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: swap.PartyRefundAddress,
                bobAddress: swap.ToAddress,
                lockTimeStamp: refundTimeUtcInSec,
                secretHash: swap.SecretHash,
                secretSize: DefaultSecretSize,
                expectedNetwork: bitcoinBased.Network);

            var side = swap.Symbol
                       .OrderSideForBuyCurrency(swap.PurchasedCurrency)
                       .Opposite();

            // get party payment
            var partyPaymentResult = await BitcoinBasedSwapInitiatedHelper
                                     .TryToFindPaymentAsync(
                swap : swap,
                currency : currency,
                side : side,
                toAddress : swap.ToAddress,
                refundAddress : swap.PartyRefundAddress,
                refundTimeStamp : refundTimeUtcInSec,
                redeemScriptBase64 : swap.PartyRedeemScript,
                cancellationToken : cancellationToken)
                                     .ConfigureAwait(false);

            if (partyPaymentResult == null || partyPaymentResult.HasError || partyPaymentResult.Value == null)
            {
                Log.Error($"BitcoinBased: can't get party payment for swap {swap.Id}");
                return;
            }

            // create redeem tx
            swap.RedeemTx = await CreateRedeemTxAsync(
                swap : swap,
                paymentTx : (IBitcoinBasedTransaction)partyPaymentResult.Value,
                redeemAddress : redeemAddress.Address,
                redeemScript : partyRedeemScript.ToBytes(),
                increaseSequenceNumber : needReplaceTx)
                            .ConfigureAwait(false);

            var toAddress = await _account
                            .GetAddressAsync(currency.Name, swap.ToAddress, cancellationToken)
                            .ConfigureAwait(false);

            // sign redeem tx
            swap.RedeemTx = await SignRedeemTxAsync(
                swap : swap,
                redeemTx : (IBitcoinBasedTransaction)swap.RedeemTx,
                paymentTx : (IBitcoinBasedTransaction)partyPaymentResult.Value,
                redeemAddress : toAddress,
                redeemScript : partyRedeemScript.ToBytes())
                            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRedeemSigned;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemSigned, cancellationToken)
            .ConfigureAwait(false);

            // broadcast redeem tx
            await BroadcastRedeemAsync(
                swap : swap,
                redeemTx : swap.RedeemTx,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            swap.StateFlags |= SwapStateFlags.IsRedeemBroadcast;

            await UpdateSwapAsync(swap, SwapStateFlags.IsRedeemBroadcast, cancellationToken)
            .ConfigureAwait(false);

            // add new unconfirmed transaction
            await _account
            .UpsertTransactionAsync(
                tx : swap.RedeemTx,
                updateBalance : true,
                cancellationToken : cancellationToken)
            .ConfigureAwait(false);

            _ = TrackTransactionConfirmationAsync(
                swap: swap,
                currency: currency,
                dataRepository: _account.DataRepository,
                txId: swap.RedeemTx.Id,
                confirmationHandler: RedeemConfirmedEventHandler,
                cancellationToken: cancellationToken);
        }
        public async Task <IBitcoinBasedTransaction> CreateSwapPaymentTxAsync(
            IEnumerable <BitcoinBasedTxOutput> fromOutputs,
            long amount,
            string refundAddress,
            string toAddress,
            DateTimeOffset lockTime,
            byte[] secretHash,
            int secretSize,
            BitcoinBasedConfig currencyConfig,
            CancellationToken cancellationToken = default)
        {
            var availableAmountInSatoshi = fromOutputs.Sum(o => o.Value);

            if (availableAmountInSatoshi <= amount)
            {
                throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}");
            }

            var feeRate = await currencyConfig
                          .GetFeeRateAsync()
                          .ConfigureAwait(false);

            var lockScript = BitcoinBasedSwapTemplate.GenerateHtlcP2PkhSwapPayment(
                aliceRefundAddress: refundAddress,
                bobAddress: toAddress,
                lockTimeStamp: lockTime.ToUnixTimeSeconds(),
                secretHash: secretHash,
                secretSize: secretSize,
                expectedNetwork: currencyConfig.Network);

            var feeInSatoshi = 0L;

            var inputsToSign = fromOutputs
                               .Select(o => new BitcoinInputToSign {
                Output = o
            });

            var destinations = new BitcoinDestination[]
            {
                new BitcoinDestination
                {
                    Script          = lockScript.PaymentScript,
                    AmountInSatoshi = amount
                }
            };

            var txParams = await BitcoinTransactionParams.SelectTransactionParamsByFeeRateAsync(
                availableInputs : inputsToSign,
                destinations : destinations,
                changeAddress : refundAddress,
                feeRate : feeRate,
                currencyConfig : currencyConfig,
                cancellationToken : cancellationToken)
                           .ConfigureAwait(false);

            if (txParams == null) // can't create tx with required feeRate => let's try to change feeRate and try again
            {
                var maxFeeInSatoshi = availableAmountInSatoshi - amount;

                var(txSize, txSizeWithChange) = BitcoinTransactionParams.CalculateTxSize(
                    inputsCount: fromOutputs.Count(),
                    inputsSize: inputsToSign.Sum(i => i.SizeWithSignature()),
                    outputsCount: destinations.Length,
                    outputsSize: destinations.Sum(d => d.Size()),
                    witnessCount: fromOutputs.Sum(o => o.IsSegWit ? 1 : 0),
                    changeOutputSize: BitcoinTransactionParams.CalculateChangeOutputSize(
                        changeAddress: refundAddress,
                        network: currencyConfig.Network));

                var estimatedFeeRate = maxFeeInSatoshi / txSizeWithChange;

                if (Math.Abs(feeRate - estimatedFeeRate) / feeRate > MaxFeeRateChangePercent)
                {
                    throw new Exception($"Insufficient funds. Available {fromOutputs.Sum(o => o.Value)}, required: {amount}. Probably feeRate has changed a lot.");
                }

                feeInSatoshi = maxFeeInSatoshi;
            }
            else
            {
                feeInSatoshi = (long)txParams.FeeInSatoshi;
            }

            var tx = currencyConfig
                     .CreateHtlcP2PkhScriptSwapPaymentTx(
                unspentOutputs: txParams?.InputsToSign.Select(i => i.Output) ?? fromOutputs,
                aliceRefundAddress: refundAddress,
                bobAddress: toAddress,
                lockTime: lockTime,
                secretHash: secretHash,
                secretSize: secretSize,
                amount: amount,
                fee: feeInSatoshi,
                redeemScript: out _);

            return(tx);
        }
Exemple #16
0
        public static async Task <Result <IBlockchainTransaction> > TryToFindPaymentAsync(
            Swap swap,
            CurrencyConfig currency,
            Side side,
            string toAddress,
            string refundAddress,
            long refundTimeStamp,
            string redeemScriptBase64           = null,
            CancellationToken cancellationToken = default)
        {
            try
            {
                Log.Debug("BitcoinBased: try to find payment tx");

                var bitcoinBased = (BitcoinBasedConfig)currency;

                var requiredAmount          = AmountHelper.QtyToSellAmount(side, swap.Qty, swap.Price, bitcoinBased.DigitsMultiplier);
                var requiredAmountInSatoshi = bitcoinBased.CoinToSatoshi(requiredAmount);

                var redeemScript = refundAddress == null && redeemScriptBase64 != null
                    ? new Script(Convert.FromBase64String(redeemScriptBase64))
                    : BitcoinBasedSwapTemplate
                                   .GenerateHtlcP2PkhSwapPayment(
                    aliceRefundAddress: refundAddress,
                    bobAddress: toAddress,
                    lockTimeStamp: refundTimeStamp,
                    secretHash: swap.SecretHash,
                    secretSize: CurrencySwap.DefaultSecretSize,
                    expectedNetwork: bitcoinBased.Network);

                var redeemScriptAddress = redeemScript
                                          .PaymentScript
                                          .GetDestinationAddress(bitcoinBased.Network)
                                          .ToString();

                var api = bitcoinBased.BlockchainApi as BitcoinBasedBlockchainApi;

                var outputsResult = await api
                                    .GetOutputsAsync(redeemScriptAddress, null, cancellationToken)
                                    .ConfigureAwait(false);

                if (outputsResult == null)
                {
                    return(new Error(Errors.RequestError, $"Connection error while getting outputs for {redeemScriptAddress} address"));
                }

                if (outputsResult.HasError)
                {
                    return(outputsResult.Error);
                }

                foreach (var output in outputsResult.Value)
                {
                    var o = output as BitcoinBasedTxOutput;

                    var outputScriptHex = o.Coin.TxOut.ScriptPubKey.ToHex();

                    if (redeemScript.PaymentScript.ToHex() != outputScriptHex)
                    {
                        continue;
                    }

                    if (o.Value < requiredAmountInSatoshi)
                    {
                        continue;
                    }

                    var txResult = await api
                                   .GetTransactionAsync(o.TxId, cancellationToken)
                                   .ConfigureAwait(false);

                    if (txResult == null)
                    {
                        return(new Error(Errors.RequestError, $"Connection error while getting tx {o.TxId}"));
                    }

                    if (txResult.HasError)
                    {
                        return(txResult.Error);
                    }

                    if (txResult.Value == null)
                    {
                        continue;
                    }

                    return(txResult.Value as BitcoinBasedTransaction);
                }

                return(new Result <IBlockchainTransaction>((IBitcoinBasedTransaction)null));
            }
            catch (Exception e)
            {
                Log.Error(e, "BitcoinBased swap initiated control task error");

                return(new Error(Errors.InternalError, e.Message));
            }
        }