Пример #1
0
        public void NotEnoughFundsLogWarning()
        {
            // Throw a 'no spendable transactions' exception
            this.federationWalletTransactionHandler.Setup(x => x.BuildTransaction(It.IsAny <TransactionBuildContext>()))
            .Throws(new WalletException(FederationWalletTransactionHandler.NotEnoughFundsMessage));

            var txBuilder = new WithdrawalTransactionBuilder(
                this.loggerFactory.Object,
                this.network,
                this.federationWalletManager.Object,
                this.federationWalletTransactionHandler.Object,
                this.federationGatewaySettings.Object,
                this.signals.Object
                );

            var recipient = new Recipient
            {
                Amount       = Money.Coins(101),
                ScriptPubKey = new Script()
            };

            Transaction ret = txBuilder.BuildWithdrawalTransaction(uint256.One, 100, recipient);

            // Log out a warning in this case, not an error.
            this.logger.Verify(x => x.Log <object>(LogLevel.Warning, It.IsAny <EventId>(), It.IsAny <object>(), null, It.IsAny <Func <object, Exception, string> >()));
        }
        public void NotEnoughFundsLogWarning()
        {
            // Throw a 'no spendable transactions' exception
            this.federationWalletTransactionHandler.Setup(x => x.BuildTransaction(It.IsAny <TransactionBuildContext>()))
            .Throws(new WalletException(FederationWalletTransactionHandler.NotEnoughFundsMessage));

            var txBuilder = new WithdrawalTransactionBuilder(
                this.network,
                this.federationWalletManager.Object,
                this.federationWalletTransactionHandler.Object,
                this.federationGatewaySettings.Object,
                this.signals.Object,
                null
                );

            var recipient = new Recipient
            {
                Amount       = Money.Coins(101),
                ScriptPubKey = new Script()
            };

            Transaction ret = txBuilder.BuildWithdrawalTransaction(0, uint256.One, 100, recipient);

            // Log out a warning in this case, not an error.
            this.logger
            .Setup(f => f.Log(It.IsAny <LogLevel>(), It.IsAny <EventId>(), It.IsAny <It.IsAnyType>(), It.IsAny <Exception>(), (Func <It.IsAnyType, Exception, string>)It.IsAny <object>()))
            .Callback(new InvocationAction(invocation =>
            {
                ((LogLevel)invocation.Arguments[0]).Should().Be(LogLevel.Warning);
            }));
        }
Пример #3
0
        public void FeeIsTakenFromRecipient()
        {
            Script redeemScript = PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, new[] { new Key().PubKey, new Key().PubKey });

            this.federationWalletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <int>()))
            .Returns(new List <UnspentOutputReference>
            {
                new UnspentOutputReference
                {
                    Transaction = new FederatedPeg.Wallet.TransactionData
                    {
                        Amount       = Money.Coins(105),
                        Id           = uint256.One,
                        ScriptPubKey = redeemScript.Hash.ScriptPubKey
                    }
                }
            });

            this.federationWalletManager.Setup(x => x.GetWallet())
            .Returns(new FederationWallet
            {
                MultiSigAddress = new MultiSigAddress
                {
                    RedeemScript = redeemScript
                }
            });

            var txBuilder = new WithdrawalTransactionBuilder(
                this.loggerFactory.Object,
                this.network,
                this.federationWalletManager.Object,
                this.federationWalletTransactionHandler.Object,
                this.federationGatewaySettings.Object,
                this.signals.Object,
                null
                );

            var recipient = new Recipient
            {
                Amount       = Money.Coins(101),
                ScriptPubKey = new Script()
            };

            Transaction ret = txBuilder.BuildWithdrawalTransaction(0, uint256.One, 100, recipient);

            Assert.NotNull(ret);

            // Fee taken from amount should be the total fee.
            Money expectedAmountAfterFee = recipient.Amount - FederatedPegSettings.CrossChainTransferFee;

            this.federationWalletTransactionHandler.Verify(x => x.BuildTransaction(It.Is <TransactionBuildContext>(y => y.Recipients.First().Amount == expectedAmountAfterFee)));

            // Fee used to send transaction should be a smaller amount.
            Money expectedTxFee = FederatedPegSettings.BaseTransactionFee + 1 * FederatedPegSettings.InputTransactionFee;

            this.federationWalletTransactionHandler.Verify(x => x.BuildTransaction(It.Is <TransactionBuildContext>(y => y.TransactionFee == expectedTxFee)));
        }
Пример #4
0
        public void FeeIsTakenFromRecipient()
        {
            var txBuilder = new WithdrawalTransactionBuilder(
                this.loggerFactory.Object,
                this.network,
                this.federationWalletManager.Object,
                this.federationWalletTransactionHandler.Object,
                this.federationGatewaySettings.Object
                );

            var recipient = new Recipient
            {
                Amount       = Money.Coins(101),
                ScriptPubKey = new Script()
            };

            Transaction ret = txBuilder.BuildWithdrawalTransaction(uint256.One, 100, recipient);

            Assert.NotNull(ret);

            Money expectedAmountAfterFee = recipient.Amount - this.federationGatewaySettings.Object.TransactionFee;

            this.federationWalletTransactionHandler.Verify(x => x.BuildTransaction(It.Is <TransactionBuildContext>(y => y.Recipients.First().Amount == expectedAmountAfterFee)));
        }
        /// <inheritdoc />
        public Transaction BuildWithdrawalTransaction(int blockHeight, uint256 depositId, uint blockTime, Recipient recipient)
        {
            try
            {
                this.logger.LogDebug("BuildDeterministicTransaction depositId(opReturnData)={0}; recipient.ScriptPubKey={1}; recipient.Amount={2}; height={3}", depositId, recipient.ScriptPubKey, recipient.Amount, blockHeight);

                // Build the multisig transaction template.
                uint256 opReturnData   = depositId;
                string  walletPassword = this.federationWalletManager.Secret.WalletPassword;
                bool    sign           = (walletPassword ?? "") != "";

                var multiSigContext = new TransactionBuildContext(new List <Recipient>(), opReturnData: opReturnData.ToBytes())
                {
                    MinConfirmations = MinConfirmations,
                    Shuffle          = false,
                    IgnoreVerify     = true,
                    WalletPassword   = walletPassword,
                    Sign             = sign,
                    Time             = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null
                };

                // Withdrawals from the sidechain won't have the OP_RETURN transaction tag, so we need to check against the ScriptPubKey of the Cirrus Dummy address.
                if (!this.federatedPegSettings.IsMainChain && recipient.ScriptPubKey.Length > 0 && recipient.ScriptPubKey == BitcoinAddress.Create(this.network.CirrusRewardDummyAddress).ScriptPubKey)
                {
                    // Use the distribution manager to determine the actual list of recipients.
                    // TODO: This would probably be neater if it was moved to the CCTS with the current method accepting a list of recipients instead
                    multiSigContext.Recipients = this.distributionManager.Distribute(blockHeight, recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee).Amount); // Reduce the overall amount by the fee first before splitting it up.

                    // This should never happen as we should always have at least one federation member with a configured wallet.
                    if (multiSigContext.Recipients.Count == 0)
                    {
                        this.logger.LogWarning("Could not identify recipients for the distribution transaction, adding dummy recipient to avoid the CCTS suspending irrecoverably.");
                        multiSigContext.Recipients = new List <Recipient> {
                            recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee)
                        };
                    }
                }
                else
                {
                    multiSigContext.Recipients = new List <Recipient> {
                        recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee)
                    };                                                                                                                                  // The fee known to the user is taken.
                }

                // TODO: Amend this so we're not picking coins twice.
                (List <Coin> coins, List <Wallet.UnspentOutputReference> unspentOutputs) = FederationWalletTransactionHandler.DetermineCoins(this.federationWalletManager, this.network, multiSigContext, this.federatedPegSettings);

                if (coins.Count > FederatedPegSettings.MaxInputs)
                {
                    this.logger.LogDebug("Too many inputs. Triggering the consolidation process.");
                    this.signals.Publish(new WalletNeedsConsolidation(recipient.Amount));
                    this.logger.LogTrace("(-)[CONSOLIDATING_INPUTS]");
                    return(null);
                }

                multiSigContext.TransactionFee   = this.federatedPegSettings.GetWithdrawalTransactionFee(coins.Count); // The "actual fee". Everything else goes to the fed.
                multiSigContext.SelectedInputs   = unspentOutputs.Select(u => u.ToOutPoint()).ToList();
                multiSigContext.AllowOtherInputs = false;

                // Build the transaction.
                Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext);

                this.logger.LogDebug("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer));

                return(transaction);
            }
            catch (Exception error)
            {
                if (error is WalletException walletException &&
                    (walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage ||
                     walletException.Message == FederationWalletTransactionHandler.NotEnoughFundsMessage))
                {
                    this.logger.LogWarning("Not enough spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block.");
                }
        /// <inheritdoc />
        public Transaction BuildWithdrawalTransaction(uint256 depositId, uint blockTime, Recipient recipient)
        {
            try
            {
                this.logger.LogInformation("BuildDeterministicTransaction depositId(opReturnData)={0} recipient.ScriptPubKey={1} recipient.Amount={2}", depositId, recipient.ScriptPubKey, recipient.Amount);

                // Build the multisig transaction template.
                uint256 opReturnData    = depositId;
                string  walletPassword  = this.federationWalletManager.Secret.WalletPassword;
                bool    sign            = (walletPassword ?? "") != "";
                var     multiSigContext = new TransactionBuildContext(new[]
                {
                    recipient.WithPaymentReducedByFee(this.federationGatewaySettings.TransactionFee)
                }.ToList(), opReturnData: opReturnData.ToBytes())
                {
                    TransactionFee   = this.federationGatewaySettings.TransactionFee,
                    MinConfirmations = MinConfirmations,
                    Shuffle          = false,
                    IgnoreVerify     = true,
                    WalletPassword   = walletPassword,
                    Sign             = sign,
                    Time             = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null
                };

                // Build the transaction.
                Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext);

                this.logger.LogInformation("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer));

                return(transaction);
            }
            catch (Exception error)
            {
                if (error is WalletException walletException && walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage)
                {
                    this.logger.LogWarning("No spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block.");
                }
Пример #7
0
        /// <inheritdoc />
        public Transaction BuildWithdrawalTransaction(uint256 depositId, uint blockTime, Recipient recipient)
        {
            try
            {
                this.logger.LogInformation("BuildDeterministicTransaction depositId(opReturnData)={0} recipient.ScriptPubKey={1} recipient.Amount={2}", depositId, recipient.ScriptPubKey, recipient.Amount);

                // Build the multisig transaction template.
                uint256 opReturnData   = depositId;
                string  walletPassword = this.federationWalletManager.Secret.WalletPassword;
                bool    sign           = (walletPassword ?? "") != "";

                var multiSigContext = new TransactionBuildContext(new List <Recipient>(), opReturnData: opReturnData.ToBytes())
                {
                    MinConfirmations = MinConfirmations,
                    Shuffle          = false,
                    IgnoreVerify     = true,
                    WalletPassword   = walletPassword,
                    Sign             = sign,
                    Time             = this.network.Consensus.IsProofOfStake ? blockTime : (uint?)null
                };

                multiSigContext.Recipients = new List <Recipient> {
                    recipient.WithPaymentReducedByFee(FederatedPegSettings.CrossChainTransferFee)
                };                                                                                                                                  // The fee known to the user is taken.

                // TODO: Amend this so we're not picking coins twice.
                (List <Coin> coins, List <Wallet.UnspentOutputReference> unspentOutputs) = FederationWalletTransactionHandler.DetermineCoins(this.federationWalletManager, this.network, multiSigContext, this.federatedPegSettings);

                multiSigContext.TransactionFee   = this.federatedPegSettings.GetWithdrawalTransactionFee(coins.Count); // The "actual fee". Everything else goes to the fed.
                multiSigContext.SelectedInputs   = unspentOutputs.Select(u => u.ToOutPoint()).ToList();
                multiSigContext.AllowOtherInputs = false;

                // Build the transaction.
                Transaction transaction = this.federationWalletTransactionHandler.BuildTransaction(multiSigContext);

                this.logger.LogInformation("transaction = {0}", transaction.ToString(this.network, RawFormat.BlockExplorer));

                return(transaction);
            }
            catch (Exception error)
            {
                if (error is WalletException walletException &&
                    (walletException.Message == FederationWalletTransactionHandler.NoSpendableTransactionsMessage ||
                     walletException.Message == FederationWalletTransactionHandler.NotEnoughFundsMessage))
                {
                    this.logger.LogWarning("Not enough spendable transactions in the wallet. Should be resolved when a pending transaction is included in a block.");
                }