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); })); }
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))); }
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."); }
/// <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."); }