示例#1
0
        public IActionResult Generate([FromBody] MiningRequest request)
        {
            Guard.NotNull(request, nameof(request));

            try
            {
                if (this.network.Consensus.IsProofOfStake && (this.consensusManager.Tip.Height > this.network.Consensus.LastPOWBlock))
                {
                    return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.MethodNotAllowed, "Method not allowed", string.Format(LastPowBlockExceededMessage, this.network.Consensus.LastPOWBlock)));
                }

                if (!this.ModelState.IsValid)
                {
                    IEnumerable <string> errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
                    return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors)));
                }

                int blockCount = request.BlockCount;

                if (blockCount <= 0)
                {
                    return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.Forbidden, "Invalid request", "The number of blocks to mine must be higher than zero."));
                }

                this.logger.LogDebug("({0}:{1})", nameof(request.BlockCount), blockCount);

                WalletAccountReference accountReference = this.GetAccount();
                HdAddress address = this.walletManager.GetUnusedAddress(accountReference);

                var generateBlocksModel = new GenerateBlocksModel
                {
                    Blocks = this.powMining.GenerateBlocks(new ReserveScript(address.Pubkey), (ulong)blockCount, int.MaxValue)
                };

                this.logger.LogTrace("(-):*.{0}={1}", "Generated block count", generateBlocksModel.Blocks.Count);

                return(this.Json(generateBlocksModel));
            }
            catch (Exception e)
            {
                this.logger.LogError(ExceptionOccurredMessage, e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public ListTransactionsModel[] ListTransactions(string account = "*", int count = 10, int skip = 0, bool include_watchonly = true)
        {
            List <ListTransactionsModel> result           = new List <ListTransactionsModel>();
            WalletAccountReference       accountReference = this.GetWalletAccountReference();

            if (include_watchonly)
            {
                var selectedWatchOnlyTransactions = this.watchOnlyWalletManager.GetWatchedTransactions().Values
                                                    .Skip(skip)
                                                    .Take(count);
                foreach (var transactionData in selectedWatchOnlyTransactions)
                {
                    var transactionInfo   = this.GetTransactionInfo(transactionData.Id);
                    var transactionResult = this.GetTransactionsModel(transactionInfo);
                    result.Add(transactionResult);
                }
            }

            Wallet.Types.Wallet    wallet        = this.walletManager.GetWallet(accountReference.WalletName);
            Func <HdAccount, bool> accountFilter = null;

            if (account == "*" || account == null)
            {
                accountFilter = Wallet.Types.Wallet.AllAccounts;
            }
            else
            {
                accountFilter = a => a.Name == account;
            }
            IEnumerable <TransactionOutputData> selectedTransactions = wallet.GetAllTransactions(accountFilter)
                                                                       .Skip(skip)
                                                                       .Take(count);

            foreach (var transactionData in selectedTransactions)
            {
                var transactionInfo   = this.GetTransactionInfo(transactionData.Id);
                var transactionResult = this.GetTransactionsModel(transactionInfo);
                result.Add(transactionResult);
            }

            return(result.ToArray());
        }
示例#3
0
        public void CanFundRawTransactionWithChangePositionSpecified()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                CoreNode node = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StraxRegTest150Miner).Start();

                var tx = this.network.CreateTransaction();
                tx.Outputs.Add(new TxOut(Money.Coins(1.1m), new Key().ScriptPubKey));
                tx.Outputs.Add(new TxOut(Money.Coins(1.2m), new Key().ScriptPubKey));
                tx.Outputs.Add(new TxOut(Money.Coins(1.3m), new Key().ScriptPubKey));
                tx.Outputs.Add(new TxOut(Money.Coins(1.4m), new Key().ScriptPubKey));

                Money totalSent = tx.TotalOut;

                // We specifically don't want to use the first available account as that is where the node has been mining to, and that is where the
                // fundrawtransaction RPC will by default get a change address from.
                var account = node.FullNode.WalletManager().GetUnusedAccount("mywallet", "password");

                var walletAccountReference = new WalletAccountReference("mywallet", account.Name);
                var changeAddress          = node.FullNode.WalletManager().GetUnusedChangeAddress(walletAccountReference);

                var options = new FundRawTransactionOptions()
                {
                    ChangeAddress  = BitcoinAddress.Create(changeAddress.Address, this.network).ToString(),
                    ChangePosition = 2
                };

                FundRawTransactionResponse funded = node.CreateRPCClient().FundRawTransaction(tx, options);

                Money fee = this.CheckFunding(node, funded.Transaction);

                Money totalInputs = this.GetTotalInputValue(node, funded.Transaction);

                Assert.Equal(new Money(this.network.MinRelayTxFee), fee);

                Assert.Equal(2, funded.ChangePos);
                Assert.Equal(changeAddress.ScriptPubKey, funded.Transaction.Outputs[funded.ChangePos].ScriptPubKey);

                // Check that the value of the change in the specified position is the expected value.
                Assert.Equal(totalInputs - totalSent - fee, funded.Transaction.Outputs[funded.ChangePos].Value);
            }
        }
        /// <summary>
        /// Finds first available wallet and its account.
        /// </summary>
        /// <returns>Reference to wallet account.</returns>
        private WalletAccountReference GetAccount()
        {
            string walletName = this.walletManager.GetWalletsNames().FirstOrDefault();

            if (walletName == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "No wallet found");
            }

            HdAccount account = this.walletManager.GetAccounts(walletName).FirstOrDefault();

            if (account == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "No account found on wallet");
            }

            var res = new WalletAccountReference(walletName, account.Name);

            return(res);
        }
示例#5
0
        private TransactionBuildContext GetOfflineWithdrawalBuildContext(string receivingAddress, string walletName, string accountName, Money amount, Money feeAmount, bool subtractFeeFromAmount)
        {
            // We presume that the amount given by the user is accurate and optimistically pass it to the build context.
            var recipient = new List <Recipient>()
            {
                new Recipient()
                {
                    Amount = amount, ScriptPubKey = BitcoinAddress.Create(receivingAddress, this.network).ScriptPubKey, SubtractFeeFromAmount = subtractFeeFromAmount
                }
            };

            var hotAccountReference = new WalletAccountReference(walletName, accountName);

            // As a simplification, the change address is defaulted to be the same cold staking script the UTXOs originate from.
            UnspentOutputReference coldStakingUtxo = this.GetSpendableTransactionsInAccount(hotAccountReference, 0).FirstOrDefault();

            if (coldStakingUtxo == null)
            {
                throw new WalletException("No unspent transactions found in cold staking hot account.");
            }

            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = hotAccountReference,
                TransactionFee   = feeAmount,
                MinConfirmations = 0,
                Shuffle          = true,                                     // We shuffle transaction outputs by default as it's better for anonymity.
                Recipients       = recipient,
                ChangeScript     = coldStakingUtxo.Transaction.ScriptPubKey, // We specifically use this instead of ChangeAddress.

                Sign = false
            };

            // As we don't actually know when the signed offline transaction will be broadcast, give it the highest chance of success if no fee was specified.
            if (context.TransactionFee == null)
            {
                context.FeeType = FeeType.High;
            }

            return(context);
        }
        /// <summary>Gets scriptPubKey from the wallet.</summary>
        private Script GetScriptPubKeyFromWallet()
        {
            string walletName = this.walletManager.GetWalletsNames().FirstOrDefault();

            if (walletName == null)
            {
                return(null);
            }

            HdAccount account = this.walletManager.GetAccounts(walletName).FirstOrDefault();

            if (account == null)
            {
                return(null);
            }

            var walletAccountReference = new WalletAccountReference(walletName, account.Name);

            HdAddress address = this.walletManager.GetUnusedAddress(walletAccountReference);

            return(address.Pubkey);
        }
示例#7
0
        /// <summary>
        /// Creates a cold staking withdrawal <see cref="Transaction"/>.
        /// </summary>
        /// <remarks>
        /// Cold staking withdrawal is performed on the wallet that is in the role of the cold staking cold wallet.
        /// </remarks>
        /// <param name="walletTransactionHandler">The wallet transaction handler used to build the transaction.</param>
        /// <param name="receivingAddress">The address that will receive the withdrawal.</param>
        /// <param name="walletName">The name of the wallet in the role of cold wallet.</param>
        /// <param name="walletPassword">The wallet password.</param>
        /// <param name="amount">The amount to remove from cold staking.</param>
        /// <param name="feeAmount">The fee to pay for cold staking transaction withdrawal.</param>
        /// <param name="subtractFeeFromAmount">Set to <c>true</c> to subtract the <paramref name="feeAmount"/> from the <paramref name="amount"/>.</param>
        /// <returns>The <see cref="Transaction"/> for cold staking withdrawal.</returns>
        /// <exception cref="WalletException">Thrown if the receiving address is in a cold staking account in this wallet.</exception>
        /// <exception cref="ArgumentNullException">Thrown if the receiving address is invalid.</exception>
        internal Transaction GetColdStakingWithdrawalTransaction(IWalletTransactionHandler walletTransactionHandler, string receivingAddress,
                                                                 string walletName, string walletPassword, Money amount, Money feeAmount, bool subtractFeeFromAmount)
        {
            (TransactionBuildContext context, HdAccount coldAccount, Script destination) = this.GetWithdrawalTransactionBuildContext(receivingAddress, walletName, amount, feeAmount, subtractFeeFromAmount);

            // Build the withdrawal transaction according to the settings recorded in the context.
            Transaction transaction = walletTransactionHandler.BuildTransaction(context);

            // Map OutPoint to UnspentOutputReference.
            var accountReference = new WalletAccountReference(walletName, coldAccount.Name);
            Dictionary <OutPoint, UnspentOutputReference> mapOutPointToUnspentOutput = this.GetSpendableTransactionsInAccount(accountReference)
                                                                                       .ToDictionary(unspent => unspent.ToOutPoint(), unspent => unspent);

            // Set the cold staking scriptPubKey on the change output.
            TxOut changeOutput = transaction.Outputs.SingleOrDefault(output => (output.ScriptPubKey != destination) && (output.Value != 0));

            if (changeOutput != null)
            {
                // Find the largest input.
                TxIn largestInput = transaction.Inputs.OrderByDescending(input => mapOutPointToUnspentOutput[input.PrevOut].Transaction.Amount).Take(1).Single();

                // Set the scriptPubKey of the change output to the scriptPubKey of the largest input.
                changeOutput.ScriptPubKey = mapOutPointToUnspentOutput[largestInput.PrevOut].Transaction.ScriptPubKey;
            }

            Wallet.Wallet wallet = this.GetWallet(walletName);

            // Add keys for signing inputs. This takes time so only add keys for distinct addresses.
            foreach (HdAddress address in transaction.Inputs.Select(i => mapOutPointToUnspentOutput[i.PrevOut].Address).Distinct())
            {
                context.TransactionBuilder.AddKeys(wallet.GetExtendedPrivateKeyForAddress(walletPassword, address));
            }

            // Sign the transaction.
            context.TransactionBuilder.SignTransactionInPlace(transaction);

            this.logger.LogTrace("(-):'{0}'", transaction.GetHash());
            return(transaction);
        }
        public UnspentCoinModel[] ListUnspent(int minConfirmations = 1, int maxConfirmations = 9999999, string addressesJson = null)
        {
            List <BitcoinAddress> addresses = new List <BitcoinAddress>();

            if (!string.IsNullOrEmpty(addressesJson))
            {
                JsonConvert.DeserializeObject <List <string> >(addressesJson).ForEach(i => addresses.Add(BitcoinAddress.Create(i, this.FullNode.Network)));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();
            IEnumerable <UnspentOutputReference> spendableTransactions = this.walletManager.GetSpendableTransactionsInAccount(accountReference, minConfirmations);

            var unspentCoins = new List <UnspentCoinModel>();

            foreach (var spendableTx in spendableTransactions)
            {
                if (spendableTx.Confirmations <= maxConfirmations)
                {
                    if (!addresses.Any() || addresses.Contains(BitcoinAddress.Create(spendableTx.Address.Address, this.FullNode.Network)))
                    {
                        unspentCoins.Add(new UnspentCoinModel()
                        {
                            Account         = accountReference.AccountName,
                            Address         = spendableTx.Address.Address,
                            Id              = spendableTx.Transaction.Id,
                            Index           = spendableTx.Transaction.Index,
                            Amount          = spendableTx.Transaction.Amount,
                            ScriptPubKeyHex = spendableTx.Transaction.ScriptPubKey.ToHex(),
                            RedeemScriptHex = null, // TODO: Currently don't support P2SH wallet addresses, review if we do.
                            Confirmations   = spendableTx.Confirmations,
                            IsSpendable     = !spendableTx.Transaction.IsSpent(),
                            IsSolvable      = !spendableTx.Transaction.IsSpent() // If it's spendable we assume it's solvable.
                        });
                    }
                }
            }

            return(unspentCoins.ToArray());
        }
示例#9
0
        /// <summary>
        /// Builds an unsigned transaction template for a cold staking withdrawal transaction.
        /// This requires specialised logic due to the lack of a private key for the cold account.
        /// </summary>
        /// <param name="walletTransactionHandler">The <see cref="IWalletTransactionHandler"/>.</param>
        /// <param name="receivingAddress">The receiving address.</param>
        /// <param name="walletName">The spending wallet name.</param>
        /// <param name="accountName">The spending account name.</param>
        /// <param name="amount">The amount to spend.</param>
        /// <param name="feeAmount">The fee amount.</param>
        /// <param name="subtractFeeFromAmount">Set to <c>true</c> to subtract the <paramref name="feeAmount"/> from the <paramref name="amount"/>.</param>
        /// <returns>See <see cref="BuildOfflineSignResponse"/>.</returns>
        public BuildOfflineSignResponse BuildOfflineColdStakingWithdrawalRequest(IWalletTransactionHandler walletTransactionHandler, string receivingAddress,
                                                                                 string walletName, string accountName, Money amount, Money feeAmount, bool subtractFeeFromAmount)
        {
            TransactionBuildContext context = this.GetOfflineWithdrawalBuildContext(receivingAddress, walletName, accountName, amount, feeAmount, subtractFeeFromAmount);

            Transaction transactionResult = walletTransactionHandler.BuildTransaction(context);

            var utxos     = new List <UtxoDescriptor>();
            var addresses = new List <AddressDescriptor>();

            foreach (ICoin coin in context.TransactionBuilder.FindSpentCoins(transactionResult))
            {
                utxos.Add(new UtxoDescriptor()
                {
                    Amount        = coin.TxOut.Value.ToUnit(MoneyUnit.BTC).ToString(),
                    TransactionId = coin.Outpoint.Hash.ToString(),
                    Index         = coin.Outpoint.N.ToString(),
                    ScriptPubKey  = coin.TxOut.ScriptPubKey.ToHex()
                });

                // We do not include address descriptors as the cold staking scripts are not really regarded as having addresses in the conventional sense.
                // There is also typically only a single script involved so the keypath hinting is of little use.
            }

            var hotAccountReference = new WalletAccountReference(walletName, accountName);

            // Return transaction hex and UTXO list.
            return(new BuildOfflineSignResponse()
            {
                WalletName = hotAccountReference.WalletName,
                WalletAccount = hotAccountReference.AccountName,
                Fee = context.TransactionFee.ToUnit(MoneyUnit.BTC).ToString(),
                UnsignedTransaction = transactionResult.ToHex(),
                Utxos = utxos,
                Addresses = addresses
            });
        }
示例#10
0
        public NewAddressModel GetNewAddress(string account = "", string addressType = "")
        {
            if (!string.IsNullOrEmpty(account))
            {
                throw new RPCServerException(RPCErrorCode.RPC_METHOD_DEPRECATED, "Use of 'account' parameter has been deprecated");
            }

            if (!string.IsNullOrEmpty(addressType))
            {
                // Currently segwit and bech32 addresses are not supported.
                if (!addressType.Equals("legacy", StringComparison.InvariantCultureIgnoreCase))
                {
                    throw new RPCServerException(RPCErrorCode.RPC_METHOD_NOT_FOUND, "Only address type 'legacy' is currently supported.");
                }
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            HdAddress hdAddress = this.walletManager.GetUnusedAddresses(accountReference, 1, alwaysnew: true).Single();

            string base58Address = hdAddress.Address;

            return(new NewAddressModel(base58Address));
        }
示例#11
0
        public void MiningController_Calls_GetAccount_Returns_Reference_To_Wallet_Account()
        {
            Mock <IConsensusManager> consensusManager = new Mock <IConsensusManager>();
            Mock <IPowMining>        powMining        = new Mock <IPowMining>();

            var walletManager = new Mock <IWalletManager>();

            walletManager.Setup(f => f.GetWalletsNames()).Returns(new List <string> {
                wallet
            });
            walletManager.Setup(f => f.GetAccounts(wallet)).Returns(new List <HdAccount> {
                new HdAccount {
                    Name = account
                }
            });

            this.fullNode.Setup(f => f.NodeFeature <MiningFeature>(false)).Returns(new MiningFeature(new ConnectionManagerSettings(NodeSettings.Default(this.network)), this.network, new MinerSettings(NodeSettings.Default(this.network)), NodeSettings.Default(this.network), this.loggerFactory, new Mock <ITimeSyncBehaviorState>().Object, powMining.Object, null));

            MiningController       controller             = new MiningController(consensusManager.Object, this.fullNode.Object, this.loggerFactory, this.network, new Mock <IPowMining>().Object, walletManager.Object);
            WalletAccountReference walletAccountReference = controller.GetAccount();

            Assert.Equal(account, walletAccountReference.AccountName);
            Assert.Equal(wallet, walletAccountReference.WalletName);
        }
        public void EstimateFeeWithLowFeeMatchesBuildTxLowFee()
        {
            var dataFolder = CreateDataFolder(this);

            Wallet wallet          = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
            var    accountKeys     = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
            var    spendingKeys    = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
            var    destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");

            HdAddress address = new HdAddress
            {
                Index        = 0,
                HdPath       = $"m/44'/0'/0'/0/0",
                Address      = spendingKeys.Address.ToString(),
                Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                Transactions = new List <TransactionData>()
            };

            ConcurrentChain chain = new ConcurrentChain(wallet.Network.GetGenesis().Header);

            WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address);
            TransactionData addressTransaction = address.Transactions.First();

            wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
            {
                Index             = 0,
                Name              = "account1",
                HdPath            = "m/44'/0'/0'",
                ExtendedPubKey    = accountKeys.ExtPubKey,
                ExternalAddresses = new List <HdAddress> {
                    address
                },
                InternalAddresses = new List <HdAddress>()
            });

            var walletFeePolicy = new Mock <IWalletFeePolicy>();

            walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations()))
            .Returns(new FeeRate(20000));

            var walletManager = new WalletManager(this.LoggerFactory.Object, Network.Main, chain, NodeSettings.Default(), dataFolder,
                                                  walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default);
            WalletTransactionHandler walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, chain, walletManager, walletFeePolicy.Object, Network.Main);

            walletManager.Wallets.Add(wallet);

            WalletAccountReference walletReference = new WalletAccountReference
            {
                AccountName = "account1",
                WalletName  = "myWallet1"
            };

            // Context to build requires password in order to sign transaction.
            TransactionBuildContext buildContext = CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0);

            walletTransactionHandler.BuildTransaction(buildContext);

            // Context for estimate does not need password.
            TransactionBuildContext estimateContext = CreateContext(walletReference, null, destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0);
            Money fee = walletTransactionHandler.EstimateFee(estimateContext);

            Assert.Equal(fee, buildContext.TransactionFee);
        }
        public void BuildTransactionNoChangeAdressesLeftCreatesNewChangeAddress()
        {
            DataFolder dataFolder = CreateDataFolder(this);

            var wallet          = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
            var accountKeys     = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
            var spendingKeys    = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
            var destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");

            var address = new HdAddress
            {
                Index        = 0,
                HdPath       = $"m/44'/0'/0'/0/0",
                Address      = spendingKeys.Address.ToString(),
                Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                Transactions = new List <TransactionData>()
            };

            var chain = new ConcurrentChain(wallet.Network.GetGenesis().Header);

            WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address);
            var addressTransaction = address.Transactions.First();

            wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
            {
                Index             = 0,
                Name              = "account1",
                HdPath            = "m/44'/0'/0'",
                ExtendedPubKey    = accountKeys.ExtPubKey,
                ExternalAddresses = new List <HdAddress> {
                    address
                },
                InternalAddresses = new List <HdAddress>()
            });

            var walletFeePolicy = new Mock <IWalletFeePolicy>();

            walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations()))
            .Returns(new FeeRate(20000));

            var walletManager = new WalletManager(this.LoggerFactory.Object, Network.Main, chain, NodeSettings.Default(), dataFolder,
                                                  walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default);
            var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, chain, walletManager, walletFeePolicy.Object, Network.Main);

            walletManager.Wallets.Add(wallet);

            var walletReference = new WalletAccountReference
            {
                AccountName = "account1",
                WalletName  = "myWallet1"
            };

            var context           = CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0);
            var transactionResult = walletTransactionHandler.BuildTransaction(context);

            var result = new Transaction(transactionResult.ToHex());
            var expectedChangeAddressKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0");

            Assert.Single(result.Inputs);
            Assert.Equal(addressTransaction.Id, result.Inputs[0].PrevOut.Hash);

            Assert.Equal(2, result.Outputs.Count);
            var output = result.Outputs[0];

            Assert.Equal((addressTransaction.Amount - context.TransactionFee - 7500), output.Value);
            Assert.Equal(expectedChangeAddressKeys.Address.ScriptPubKey, output.ScriptPubKey);

            output = result.Outputs[1];
            Assert.Equal(7500, output.Value);
            Assert.Equal(destinationKeys.PubKey.ScriptPubKey, output.ScriptPubKey);

            Assert.Equal(addressTransaction.Amount - context.TransactionFee, result.TotalOut);
            Assert.NotNull(transactionResult.GetHash());
            Assert.Equal(result.GetHash(), transactionResult.GetHash());
        }
示例#14
0
 private void a_miner_validating_blocks()
 {
     this.miningWalletAccountReference = new WalletAccountReference(walletName, "account 0");
 }
 /// <summary>
 /// Initialize a new instance of a <see cref="TransactionBuildContext"/>
 /// </summary>
 /// <param name="accountReference">The wallet and account from which to build this transaction</param>
 /// <param name="recipients">The target recipients to send coins to.</param>
 public TransactionBuildContext(WalletAccountReference accountReference, List <Recipient> recipients)
     : this(accountReference, recipients, string.Empty)
 {
 }
        public BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0 && addressBalance.AmountUnconfirmed == 0)
            {
                return(BuildCreateContractTransactionResponse.Failed(SenderNoBalanceError));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableInputsForAddress(request.WalletName, request.Sender);

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    object[] methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCreateContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                if (account == null)
                {
                    return(BuildCreateContractTransactionResponse.Failed($"No account with the name '{request.AccountName}' could be found."));
                }

                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);

            byte[] serializedTxData = this.callDataSerializer.Serialize(txData);

            Result <ContractTxData> deserialized = this.callDataSerializer.Deserialize(serializedTxData);

            // We also want to ensure we're sending valid data: AKA it can be deserialized.
            if (deserialized.IsFailure)
            {
                return(BuildCreateContractTransactionResponse.Failed("Invalid data. If network requires code signing, check the code contains a signature."));
            }

            // HACK
            // If requiring a signature, also check the signature.
            if (this.network is ISignedCodePubKeyHolder holder)
            {
                var  signedTxData = (SignedCodeContractTxData)deserialized.Value;
                bool validSig     = new ContractSigner().Verify(holder.SigningContractPubKey, signedTxData.ContractExecutionCode, signedTxData.CodeSignature);

                if (!validSig)
                {
                    return(BuildCreateContractTransactionResponse.Failed("Signature in code does not come from required signing key."));
                }
            }

            var recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(serializedTxData)
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#17
0
        public async Task DummyRegistration(string originWalletName, string originWalletPassword)
        {
            // TODO: Move this functionality into the tests
            var token = new List <byte>();

            // Server ID
            token.AddRange(Encoding.ASCII.GetBytes("".PadRight(34)));

            // IPv4 address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // IPv6 address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // Onion address
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);
            token.Add(0x00);

            // Port number
            byte[] portNumber = BitConverter.GetBytes(37123);
            token.Add(portNumber[0]);
            token.Add(portNumber[1]);

            // RSA sig length
            byte[] rsaLength = BitConverter.GetBytes(256);
            token.Add(rsaLength[0]);
            token.Add(rsaLength[1]);

            // RSA signature
            byte[] rsaSig = new byte[256];
            token.AddRange(rsaSig);

            // ECDSA sig length
            byte[] ecdsaLength = BitConverter.GetBytes(128);
            token.Add(ecdsaLength[0]);
            token.Add(ecdsaLength[1]);

            // ECDSA signature
            byte[] ecdsaSig = new byte[128];
            token.AddRange(ecdsaSig);

            // Configuration hash
            token.AddRange(Encoding.ASCII.GetBytes("aa4e984c5655a677716539acc8cbc0ce29331429"));

            // Finally add protocol byte and computed length to beginning of header
            byte[] protocolVersionByte = BitConverter.GetBytes(254);
            byte[] headerLength        = BitConverter.GetBytes(token.Count);

            token.Insert(0, protocolVersionByte[0]);
            token.Insert(1, headerLength[0]);
            token.Insert(2, headerLength[1]);

            Money outputValue = new Money(0.0001m, MoneyUnit.BTC);

            Transaction sendTx = new Transaction();

            // Recognisable string used to tag the transaction within the blockchain
            byte[] bytes = Encoding.UTF8.GetBytes("BREEZE_REGISTRATION_MARKER");
            sendTx.Outputs.Add(new TxOut()
            {
                Value        = outputValue,
                ScriptPubKey = TxNullDataTemplate.Instance.GenerateScriptPubKey(bytes)
            });

            // Add each data-encoding PubKey as a TxOut
            foreach (PubKey pubKey in BlockChainDataConversions.BytesToPubKeys(token.ToArray()))
            {
                TxOut destTxOut = new TxOut()
                {
                    Value        = outputValue,
                    ScriptPubKey = pubKey.ScriptPubKey
                };

                sendTx.Outputs.Add(destTxOut);
            }

            HdAccount highestAcc = null;

            foreach (HdAccount account in this.walletManager.GetAccounts(originWalletName))
            {
                if (highestAcc == null)
                {
                    highestAcc = account;
                }

                if (account.GetSpendableAmount().ConfirmedAmount > highestAcc.GetSpendableAmount().ConfirmedAmount)
                {
                    highestAcc = account;
                }
            }

            // This fee rate is primarily for regtest, testnet and mainnet have actual estimators that work
            FeeRate feeRate = new FeeRate(new Money(10000, MoneyUnit.Satoshi));
            WalletAccountReference  accountRef     = new WalletAccountReference(originWalletName, highestAcc.Name);
            List <Recipient>        recipients     = new List <Recipient>();
            TransactionBuildContext txBuildContext = new TransactionBuildContext(accountRef, recipients);

            txBuildContext.WalletPassword   = originWalletPassword;
            txBuildContext.OverrideFeeRate  = feeRate;
            txBuildContext.Sign             = true;
            txBuildContext.MinConfirmations = 0;

            this.walletTransactionHandler.FundTransaction(txBuildContext, sendTx);

            this.logger.LogDebug("Trying to broadcast transaction: " + sendTx.GetHash());

            await this.broadcasterManager.BroadcastTransactionAsync(sendTx).ConfigureAwait(false);

            var bcResult = this.broadcasterManager.GetTransaction(sendTx.GetHash()).State;

            switch (bcResult)
            {
            case Stratis.Bitcoin.Broadcasting.State.Broadcasted:
            case Stratis.Bitcoin.Broadcasting.State.Propagated:
                this.logger.LogDebug("Broadcasted transaction: " + sendTx.GetHash());
                break;

            case Stratis.Bitcoin.Broadcasting.State.ToBroadcast:
                // Wait for propagation
                var waited = TimeSpan.Zero;
                var period = TimeSpan.FromSeconds(1);
                while (TimeSpan.FromSeconds(21) > waited)
                {
                    // Check BroadcasterManager for broadcast success
                    var transactionEntry = this.broadcasterManager.GetTransaction(sendTx.GetHash());
                    if (transactionEntry != null &&
                        transactionEntry.State == Stratis.Bitcoin.Broadcasting.State.Propagated)
                    {
                        // TODO: This is cluttering up the console, only need to log it once
                        this.logger.LogDebug("Propagated transaction: " + sendTx.GetHash());
                    }
                    await Task.Delay(period).ConfigureAwait(false);

                    waited += period;
                }
                break;

            case Stratis.Bitcoin.Broadcasting.State.CantBroadcast:
                // Do nothing
                break;
            }

            this.logger.LogDebug("Uncertain if transaction was propagated: " + sendTx.GetHash());
        }
示例#18
0
        private WalletTransactionHandlerTestContext SetupWallet()
        {
            DataFolder dataFolder = CreateDataFolder(this);

            Types.Wallet wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
            (ExtKey ExtKey, string ExtPubKey)accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
            (PubKey PubKey, BitcoinPubKeyAddress Address)spendingKeys    = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
            (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");

            var address = new HdAddress
            {
                Index        = 0,
                HdPath       = $"m/44'/0'/0'/0/0",
                Address      = spendingKeys.Address.ToString(),
                Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                // Transactions = new List<TransactionData>()
            };

            var chain = new ChainIndexer(wallet.Network);

            WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.walletStore as WalletMemoryStore, wallet.Network, chain, address);
            TransactionOutputData addressTransaction = wallet.walletStore.GetForAddress(address.Address).First();

            wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
            {
                Index             = 0,
                Name              = "account1",
                HdPath            = "m/44'/0'/0'",
                ExtendedPubKey    = accountKeys.ExtPubKey,
                ExternalAddresses = new List <HdAddress> {
                    address
                },
                InternalAddresses = new List <HdAddress>()
            });

            var walletFeePolicy = new Mock <IWalletFeePolicy>();

            walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations()))
            .Returns(new FeeRate(20000));

            var walletManager = new WalletManager(this.LoggerFactory.Object, this.Network, chain,
                                                  new WalletSettings(NodeSettings.Default(this.Network)), dataFolder,
                                                  walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), DateTimeProvider.Default, this.scriptAddressReader);
            var walletTransactionHandler =
                new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, this.Network, this.standardTransactionPolicy);

            walletManager.Wallets.Add(wallet);

            var walletReference = new WalletAccountReference
            {
                AccountName = "account1",
                WalletName  = "myWallet1"
            };

            return(new WalletTransactionHandlerTestContext
            {
                Wallet = wallet,
                AccountKeys = accountKeys,
                DestinationKeys = destinationKeys,
                AddressTransaction = addressTransaction,
                WalletTransactionHandler = walletTransactionHandler,
                WalletReference = walletReference
            });
        }
示例#19
0
        private BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            this.logger.LogTrace(request.ToString());

            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0)
            {
                return(BuildCreateContractTransactionResponse.Failed($"The 'Sender' address you're trying to spend from doesn't have a confirmed balance. Current unconfirmed balance: {addressBalance.AmountUnconfirmed}. Please check the 'Sender' address."));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableTransactionsInWallet(request.WalletName, MinConfirmationsAllChecks).Where(x => x.Address.Address == request.Sender).Select(x => x.ToOutPoint()).ToList();

            ulong gasPrice = ulong.Parse(request.GasPrice);
            ulong gasLimit = ulong.Parse(request.GasLimit);

            SmartContractCarrier carrier;

            if (request.Parameters != null && request.Parameters.Any())
            {
                carrier = SmartContractCarrier.CreateContract(ReflectionVirtualMachine.VmVersion, request.ContractCode.HexToByteArray(), gasPrice, new Gas(gasLimit), request.Parameters);
            }
            else
            {
                carrier = SmartContractCarrier.CreateContract(ReflectionVirtualMachine.VmVersion, request.ContractCode.HexToByteArray(), gasPrice, new Gas(gasLimit));
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (gasPrice * gasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);
            var   recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(carrier.Serialize())
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToAddress(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
        public BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender);

            if (addressBalance.AmountConfirmed == 0 && addressBalance.AmountUnconfirmed == 0)
            {
                return(BuildCreateContractTransactionResponse.Failed(SenderNoBalanceError));
            }

            var selectedInputs = new List <OutPoint>();

            selectedInputs = this.walletManager.GetSpendableInputsForAddress(request.WalletName, request.Sender);

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    var methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCreateContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccountByCoinType(request.AccountName, this.coinType);
                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);
            var   recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(this.callDataSerializer.Serialize(txData))
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#21
0
        public async Task <uint256> SendManyAsync(string fromAccount, string addressesJson, int minConf = 1, string comment = null, string subtractFeeFromJson = null, bool isReplaceable = false, int?confTarget = null, string estimateMode = "UNSET")
        {
            if (string.IsNullOrEmpty(addressesJson))
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "No valid output addresses specified.");
            }

            var addresses = new Dictionary <string, decimal>();

            try
            {
                // Outputs addresses are key-value pairs of address, amount. Translate to Receipient list.
                addresses = JsonConvert.DeserializeObject <Dictionary <string, decimal> >(addressesJson);
            }
            catch (JsonSerializationException ex)
            {
                throw new RPCServerException(RPCErrorCode.RPC_PARSE_ERROR, ex.Message);
            }

            if (addresses.Count == 0)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "No valid output addresses specified.");
            }

            // Optional list of addresses to subtract fees from.
            IEnumerable <BitcoinAddress> subtractFeeFromAddresses = null;

            if (!string.IsNullOrEmpty(subtractFeeFromJson))
            {
                try
                {
                    subtractFeeFromAddresses = JsonConvert.DeserializeObject <List <string> >(subtractFeeFromJson).Select(i => BitcoinAddress.Create(i, this.FullNode.Network));
                }
                catch (JsonSerializationException ex)
                {
                    throw new RPCServerException(RPCErrorCode.RPC_PARSE_ERROR, ex.Message);
                }
            }

            var recipients = new List <Recipient>();

            foreach (var address in addresses)
            {
                // Check for duplicate recipients
                var recipientAddress = BitcoinAddress.Create(address.Key, this.FullNode.Network).ScriptPubKey;
                if (recipients.Any(r => r.ScriptPubKey == recipientAddress))
                {
                    throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, string.Format("Invalid parameter, duplicated address: {0}.", recipientAddress));
                }

                var recipient = new Recipient
                {
                    ScriptPubKey          = recipientAddress,
                    Amount                = Money.Coins(address.Value),
                    SubtractFeeFromAmount = subtractFeeFromAddresses == null ? false : subtractFeeFromAddresses.Contains(BitcoinAddress.Create(address.Key, this.FullNode.Network))
                };

                recipients.Add(recipient);
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            var context = new TransactionBuildContext(this.FullNode.Network)
            {
                AccountReference = accountReference,
                MinConfirmations = minConf,
                Shuffle          = true, // We shuffle transaction outputs by default as it's better for anonymity.
                Recipients       = recipients,
                CacheSecret      = false
            };

            // Set fee type for transaction build context.
            context.FeeType = FeeType.Medium;

            if (estimateMode.Equals("ECONOMICAL", StringComparison.InvariantCultureIgnoreCase))
            {
                context.FeeType = FeeType.Low;
            }
            else if (estimateMode.Equals("CONSERVATIVE", StringComparison.InvariantCultureIgnoreCase))
            {
                context.FeeType = FeeType.High;
            }

            try
            {
                // Log warnings for currently unsupported parameters.
                if (!string.IsNullOrEmpty(comment))
                {
                    this.logger.LogWarning("'comment' parameter is currently unsupported. Ignored.");
                }

                if (isReplaceable)
                {
                    this.logger.LogWarning("'replaceable' parameter is currently unsupported. Ignored.");
                }

                if (confTarget != null)
                {
                    this.logger.LogWarning("'conf_target' parameter is currently unsupported. Ignored.");
                }

                Transaction transaction = this.walletTransactionHandler.BuildTransaction(context);
                await this.broadcasterManager.BroadcastTransactionAsync(transaction);

                return(transaction.GetHash());
            }
            catch (SecurityException)
            {
                throw new RPCServerException(RPCErrorCode.RPC_WALLET_UNLOCK_NEEDED, "Wallet unlock needed");
            }
            catch (WalletException exception)
            {
                throw new RPCServerException(RPCErrorCode.RPC_WALLET_ERROR, exception.Message);
            }
            catch (NotImplementedException exception)
            {
                throw new RPCServerException(RPCErrorCode.RPC_MISC_ERROR, exception.Message);
            }
        }
示例#22
0
        /// <summary>
        ///     Creates a cold staking withdrawal <see cref="Transaction" />.
        /// </summary>
        /// <remarks>
        ///     Cold staking withdrawal is performed on the wallet that is in the role of the cold staking cold wallet.
        /// </remarks>
        /// <param name="walletTransactionHandler">The wallet transaction handler used to build the transaction.</param>
        /// <param name="receivingAddress">The address that will receive the withdrawal.</param>
        /// <param name="walletName">The name of the wallet in the role of cold wallet.</param>
        /// <param name="walletPassword">The wallet password.</param>
        /// <param name="amount">The amount to remove from cold staking.</param>
        /// <param name="feeAmount">The fee to pay for cold staking transaction withdrawal.</param>
        /// <returns>The <see cref="Transaction" /> for cold staking withdrawal.</returns>
        /// <exception cref="WalletException">Thrown if the receiving address is in a cold staking account in this wallet.</exception>
        /// <exception cref="ArgumentNullException">Thrown if the receiving address is invalid.</exception>
        internal Transaction GetColdStakingWithdrawalTransaction(IWalletTransactionHandler walletTransactionHandler,
                                                                 string receivingAddress,
                                                                 string walletName, string walletPassword, Money amount, Money feeAmount)
        {
            Guard.NotEmpty(receivingAddress, nameof(receivingAddress));
            Guard.NotEmpty(walletName, nameof(walletName));
            Guard.NotNull(amount, nameof(amount));
            Guard.NotNull(feeAmount, nameof(feeAmount));

            var wallet = GetWalletByName(walletName);

            // Get the cold staking account.
            var coldAccount = GetColdStakingAccount(wallet, true);

            if (coldAccount == null)
            {
                this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_DOES_NOT_EXIST]");
                throw new WalletException("The cold wallet account does not exist.");
            }

            // Prevent reusing cold stake addresses as regular withdrawal addresses.
            if (coldAccount.ExternalAddresses.Concat(coldAccount.InternalAddresses).Any(s =>
                                                                                        s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_COLD_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking cold wallet account.");
            }

            var hotAccount = GetColdStakingAccount(wallet, false);

            if (hotAccount != null && hotAccount.ExternalAddresses.Concat(hotAccount.InternalAddresses)
                .Any(s => s.Address == receivingAddress || s.Bech32Address == receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_HOT_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking hot wallet account.");
            }

            Script destination = null;

            if (BitcoinWitPubKeyAddress.IsValid(receivingAddress, this.network, out _))
            {
                destination = new BitcoinWitPubKeyAddress(receivingAddress, wallet.Network).ScriptPubKey;
            }
            else
            {
                // Send the money to the receiving address.
                destination = BitcoinAddress.Create(receivingAddress, wallet.Network).ScriptPubKey;
            }

            // Create the transaction build context (used in BuildTransaction).
            var accountReference = new WalletAccountReference(walletName, coldAccount.Name);
            var context          = new TransactionBuildContext(wallet.Network)
            {
                AccountReference = accountReference,
                // Specify a dummy change address to prevent a change (internal) address from being created.
                // Will be changed after the transacton is built and before it is signed.
                ChangeAddress    = coldAccount.ExternalAddresses.First(),
                TransactionFee   = feeAmount,
                MinConfirmations = 0,
                Shuffle          = false,
                Sign             = false,
                Recipients       = new[] { new Recipient {
                                               Amount = amount, ScriptPubKey = destination
                                           } }.ToList()
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            // Avoid script errors due to missing scriptSig.
            context.TransactionBuilder.StandardTransactionPolicy.ScriptVerify = null;

            // Build the transaction according to the settings recorded in the context.
            var transaction = walletTransactionHandler.BuildTransaction(context);

            // Map OutPoint to UnspentOutputReference.
            var mapOutPointToUnspentOutput = GetSpendableTransactionsInAccount(accountReference)
                                             .ToDictionary(unspent => unspent.ToOutPoint(), unspent => unspent);

            // Set the cold staking scriptPubKey on the change output.
            var changeOutput =
                transaction.Outputs.SingleOrDefault(output => output.ScriptPubKey != destination && output.Value != 0);

            if (changeOutput != null)
            {
                // Find the largest input.
                var largestInput = transaction.Inputs
                                   .OrderByDescending(input => mapOutPointToUnspentOutput[input.PrevOut].Transaction.Amount).Take(1)
                                   .Single();

                // Set the scriptPubKey of the change output to the scriptPubKey of the largest input.
                changeOutput.ScriptPubKey = mapOutPointToUnspentOutput[largestInput.PrevOut].Transaction.ScriptPubKey;
            }

            // Add keys for signing inputs. This takes time so only add keys for distinct addresses.
            foreach (var item in transaction.Inputs.Select(i => mapOutPointToUnspentOutput[i.PrevOut]).Distinct())
            {
                var prevscript = item.Transaction.ScriptPubKey;

                if (prevscript.IsScriptType(ScriptType.P2SH) || prevscript.IsScriptType(ScriptType.P2WSH))
                {
                    if (item.Address.RedeemScript == null)
                    {
                        throw new WalletException("Missing redeem script");
                    }

                    // Provide the redeem script to the builder
                    var scriptCoin = ScriptCoin.Create(this.network, item.ToOutPoint(),
                                                       new TxOut(item.Transaction.Amount, prevscript), item.Address.RedeemScript);
                    context.TransactionBuilder.AddCoins(scriptCoin);
                }

                context.TransactionBuilder.AddKeys(
                    wallet.GetExtendedPrivateKeyForAddress(walletPassword, item.Address));
            }

            // Sign the transaction.
            context.TransactionBuilder.SignTransactionInPlace(transaction);

            this.logger.LogTrace("(-):'{0}'", transaction.GetHash());
            return(transaction);
        }
示例#23
0
        public GetTransactionModel GetTransaction(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Types.Wallet wallet  = this.walletManager.GetWalletByName(accountReference.WalletName);
            HdAccount    account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName);

            // Get the transaction from the wallet by looking into received and send transactions.
            List <HdAddress>             addresses            = account.GetCombinedAddresses().ToList();
            List <TransactionOutputData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress()).SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.Id == trxid)).ToList();
            List <TransactionOutputData> sendTransactions     = addresses.SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList();

            if (!receivedTransactions.Any() && !sendTransactions.Any())
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id.");
            }

            // Get the block hash from the transaction in the wallet.
            TransactionOutputData transactionFromWallet = null;
            uint256 blockHash = null;
            int?    blockHeight, blockIndex;

            if (receivedTransactions.Any())
            {
                blockHeight           = receivedTransactions.First().BlockHeight;
                blockIndex            = receivedTransactions.First().BlockIndex;
                blockHash             = receivedTransactions.First().BlockHash;
                transactionFromWallet = receivedTransactions.First();
            }
            else
            {
                blockHeight = sendTransactions.First().SpendingDetails.BlockHeight;
                blockIndex  = sendTransactions.First().SpendingDetails.BlockIndex;
                blockHash   = blockHeight != null?this.ChainIndexer.GetHeader(blockHeight.Value).HashBlock : null;
            }

            // Get the block containing the transaction (if it has  been confirmed).
            ChainedHeaderBlock chainedHeaderBlock = null;

            if (blockHash != null)
            {
                this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> {
                    blockHash
                }, b => { chainedHeaderBlock = b; });
            }

            Block       block = null;
            Transaction transactionFromStore      = null;

            if (chainedHeaderBlock != null)
            {
                block = chainedHeaderBlock.Block;
                transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid);
            }

            DateTimeOffset transactionTime;
            bool           isGenerated;
            string         hex;

            if (transactionFromStore != null)
            {
                // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates
                if (transactionFromStore is IPosTransactionWithTime posTrx)
                {
                    transactionTime = Utils.UnixTimeToDateTime(posTrx.Time);
                }
                else
                {
                    transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time);
                }

                isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake;
                hex         = transactionFromStore.ToHex();
            }
            else if (transactionFromWallet != null)
            {
                transactionTime = transactionFromWallet.CreationTime;
                isGenerated     = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true;
                hex             = transactionFromWallet.Hex;
            }
            else
            {
                transactionTime = sendTransactions.First().SpendingDetails.CreationTime;
                isGenerated     = false;
                hex             = null; // TODO get from mempool
            }

            var model = new GetTransactionModel
            {
                Confirmations   = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0,
                Isgenerated     = isGenerated ? true : (bool?)null,
                BlockHash       = blockHash,
                BlockIndex      = blockIndex ?? block?.Transactions.FindIndex(t => t.GetHash() == trxid),
                BlockTime       = block?.Header.BlockTime.ToUnixTimeSeconds(),
                TransactionId   = uint256.Parse(txid),
                TransactionTime = transactionTime.ToUnixTimeSeconds(),
                TimeReceived    = transactionTime.ToUnixTimeSeconds(),
                Details         = new List <GetTransactionDetailsModel>(),
                Hex             = hex
            };

            Money feeSent = Money.Zero;

            if (sendTransactions.Any())
            {
                feeSent = wallet.GetSentTransactionFee(trxid);
            }

            // Send transactions details.
            foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments))
            {
                // Only a single item should appear per destination address.
                if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null)
                {
                    model.Details.Add(new GetTransactionDetailsModel
                    {
                        Address     = paymentDetail.DestinationAddress,
                        Category    = GetTransactionDetailsCategoryModel.Send,
                        Amount      = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC),
                        Fee         = -feeSent.ToDecimal(MoneyUnit.BTC),
                        OutputIndex = paymentDetail.OutputIndex
                    });
                }
            }

            // Get the ColdStaking script template if available.
            Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates();
            ScriptTemplate coldStakingTemplate            = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null;

            // Receive transactions details.
            foreach (TransactionOutputData trxInWallet in receivedTransactions)
            {
                // Skip the details if the script pub key is cold staking.
                // TODO: Verify if we actually need this any longer, after changing the internals to recognice account type!
                if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey))
                {
                    continue;
                }

                GetTransactionDetailsCategoryModel category;

                if (isGenerated)
                {
                    category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature;
                }
                else
                {
                    category = GetTransactionDetailsCategoryModel.Receive;
                }

                model.Details.Add(new GetTransactionDetailsModel
                {
                    Address     = trxInWallet.Address,
                    Category    = category,
                    Amount      = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC),
                    OutputIndex = trxInWallet.Index
                });
            }

            model.Amount = model.Details.Sum(d => d.Amount);
            model.Fee    = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee;

            return(model);
        }
示例#24
0
        public Task <ListSinceBlockModel> ListSinceBlockAsync(string blockHash, int targetConfirmations = 1)
        {
            ChainedHeader headerBlock = null;

            if (!string.IsNullOrEmpty(blockHash) && uint256.TryParse(blockHash, out uint256 hashBlock))
            {
                headerBlock = this.ChainIndexer.GetHeader(hashBlock);
            }

            if (!string.IsNullOrEmpty(blockHash) && headerBlock == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
            }

            if (targetConfirmations < 1)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "Invalid parameter");
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();

            Types.Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName);

            IEnumerable <TransactionOutputData> transactions = wallet.GetAllTransactions();

            var model = new ListSinceBlockModel();

            foreach (TransactionOutputData transactionData in transactions)
            {
                GetTransactionModel transaction = this.GetTransaction(transactionData.Id.ToString());

                int blockHeight = transactionData.BlockHeight ?? 0;

                if (headerBlock != null && blockHeight < headerBlock.Height)
                {
                    continue;
                }

                if (transaction.Confirmations < targetConfirmations)
                {
                    continue;
                }

                ListSinceBlockTransactionCategoryModel category = GetListSinceBlockTransactionCategoryModel(transaction);

                model.Transactions.Add(new ListSinceBlockTransactionModel
                {
                    Confirmations   = transaction.Confirmations,
                    BlockHash       = transaction.BlockHash,
                    BlockIndex      = transaction.BlockIndex,
                    BlockTime       = transaction.BlockTime,
                    TransactionId   = transaction.TransactionId,
                    TransactionTime = transaction.TransactionTime,
                    TimeReceived    = transaction.TimeReceived,
                    Account         = accountReference.AccountName,
                    Address         = transactionData.ScriptPubKey?.GetDestinationAddress(this.Network)?.ToString(),
                    Amount          = transaction.Amount,
                    Category        = category,
                    Fee             = transaction.Fee
                });
            }

            model.LastBlock = this.ChainIndexer.Tip.HashBlock;

            return(Task.FromResult(model));
        }
        public void BuildTransactionFeeTooLowThrowsWalletException()
        {
            Assert.Throws <WalletException>(() =>
            {
                var walletFeePolicy = new Mock <IWalletFeePolicy>();
                walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations()))
                .Returns(new FeeRate(0));

                var wallet          = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
                var accountKeys     = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
                var spendingKeys    = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
                var destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");
                var changeKeys      = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0");

                var address = new HdAddress
                {
                    Index        = 0,
                    HdPath       = $"m/44'/0'/0'/0/0",
                    Address      = spendingKeys.Address.ToString(),
                    Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                    ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                    Transactions = new List <TransactionData>()
                };

                var chain = new ConcurrentChain(wallet.Network.GetGenesis().Header);
                WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.Network, chain, address);

                wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
                {
                    Index             = 0,
                    Name              = "account1",
                    HdPath            = "m/44'/0'/0'",
                    ExtendedPubKey    = accountKeys.ExtPubKey,
                    ExternalAddresses = new List <HdAddress> {
                        address
                    },
                    InternalAddresses = new List <HdAddress>
                    {
                        new HdAddress {
                            Index        = 0,
                            HdPath       = $"m/44'/0'/0'/1/0",
                            Address      = changeKeys.Address.ToString(),
                            Pubkey       = changeKeys.PubKey.ScriptPubKey,
                            ScriptPubKey = changeKeys.Address.ScriptPubKey,
                            Transactions = new List <TransactionData>()
                        }
                    }
                });

                var walletManager = new WalletManager(this.LoggerFactory.Object, Network.Main, chain, NodeSettings.Default(),
                                                      new DataFolder(new NodeSettings {
                    DataDir = "TestData/WalletTransactionHandlerTest/BuildTransactionFeeTooLowThrowsWalletException"
                }), walletFeePolicy.Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default);
                var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, chain, walletManager, walletFeePolicy.Object, Network.Main);

                walletManager.Wallets.Add(wallet);

                var walletReference = new WalletAccountReference
                {
                    AccountName = "account1",
                    WalletName  = "myWallet1"
                };

                walletTransactionHandler.BuildTransaction(CreateContext(walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0));
            });
        }
示例#26
0
        public BuildCreateContractTransactionResponse BuildCreateTx(BuildCreateContractTransactionRequest request)
        {
            if (!this.CheckBalance(request.Sender))
            {
                return(BuildCreateContractTransactionResponse.Failed(SenderNoBalanceError));
            }

            List <OutPoint> selectedInputs = this.SelectInputs(request.WalletName, request.Sender, request.Outpoints);

            if (!selectedInputs.Any())
            {
                return(BuildCreateContractTransactionResponse.Failed("Invalid list of request outpoints have been passed to the method. Please ensure that the outpoints are spendable by the sender address"));
            }

            ContractTxData txData;

            if (request.Parameters != null && request.Parameters.Any())
            {
                try
                {
                    object[] methodParameters = this.methodParameterStringSerializer.Deserialize(request.Parameters);
                    txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasPrice, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasLimit, request.ContractCode.HexToByteArray(), methodParameters);
                }
                catch (MethodParameterStringSerializerException exception)
                {
                    return(BuildCreateContractTransactionResponse.Failed(exception.Message));
                }
            }
            else
            {
                txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasPrice, (Stratis.SmartContracts.RuntimeObserver.Gas)request.GasLimit, request.ContractCode.HexToByteArray());
            }

            HdAddress senderAddress = null;

            if (!string.IsNullOrWhiteSpace(request.Sender))
            {
                Features.Wallet.Wallet wallet = this.walletManager.GetWallet(request.WalletName);
                HdAccount account             = wallet.GetAccount(request.AccountName);
                if (account == null)
                {
                    return(BuildCreateContractTransactionResponse.Failed($"No account with the name '{request.AccountName}' could be found."));
                }

                senderAddress = account.GetCombinedAddresses().FirstOrDefault(x => x.Address == request.Sender);
            }

            ulong totalFee = (request.GasPrice * request.GasLimit) + Money.Parse(request.FeeAmount);
            var   walletAccountReference = new WalletAccountReference(request.WalletName, request.AccountName);

            byte[] serializedTxData = this.callDataSerializer.Serialize(txData);

            Result <ContractTxData> deserialized = this.callDataSerializer.Deserialize(serializedTxData);

            // We also want to ensure we're sending valid data: AKA it can be deserialized.
            if (deserialized.IsFailure)
            {
                return(BuildCreateContractTransactionResponse.Failed("Invalid data. If network requires code signing, check the code contains a signature."));
            }

            var recipient = new Recipient {
                Amount = request.Amount ?? "0", ScriptPubKey = new Script(serializedTxData)
            };
            var context = new TransactionBuildContext(this.network)
            {
                AccountReference = walletAccountReference,
                TransactionFee   = totalFee,
                ChangeAddress    = senderAddress,
                SelectedInputs   = selectedInputs,
                MinConfirmations = MinConfirmationsAllChecks,
                WalletPassword   = request.Password,
                Recipients       = new[] { recipient }.ToList()
            };

            try
            {
                Transaction transaction     = this.walletTransactionHandler.BuildTransaction(context);
                uint160     contractAddress = this.addressGenerator.GenerateAddress(transaction.GetHash(), 0);
                return(BuildCreateContractTransactionResponse.Succeeded(transaction, context.TransactionFee, contractAddress.ToBase58Address(this.network)));
            }
            catch (Exception exception)
            {
                return(BuildCreateContractTransactionResponse.Failed(exception.Message));
            }
        }
示例#27
0
        public void FundTransaction_Given__a_wallet_has_enough_inputs__When__adding_inputs_to_an_existing_transaction__Then__the_transaction_is_funded_successfully()
        {
            DataFolder dataFolder = CreateDataFolder(this);

            Types.Wallet wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
            (ExtKey ExtKey, string ExtPubKey)accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
            (PubKey PubKey, BitcoinPubKeyAddress Address)spendingKeys     = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
            (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys1 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");
            (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys2 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/2");
            (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys3 = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/3");

            var address = new HdAddress
            {
                Index        = 0,
                HdPath       = $"m/44'/0'/0'/0/0",
                Address      = spendingKeys.Address.ToString(),
                Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                // Transactions = new List<TransactionData>()
            };

            // wallet with 4 coinbase outputs of 50 = 200 Bitcoin
            var chain = new ChainIndexer(wallet.Network);

            WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.walletStore as WalletMemoryStore, wallet.Network, chain, address, 4);

            wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
            {
                Index             = 0,
                Name              = "account1",
                HdPath            = "m/44'/0'/0'",
                ExtendedPubKey    = accountKeys.ExtPubKey,
                ExternalAddresses = new List <HdAddress> {
                    address
                },
                InternalAddresses = new List <HdAddress>()
            });

            var walletFeePolicy = new Mock <IWalletFeePolicy>();

            walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations())).Returns(new FeeRate(20000));
            var overrideFeeRate = new FeeRate(20000);

            var walletManager = new WalletManager(this.LoggerFactory.Object, this.Network, chain, new WalletSettings(NodeSettings.Default(this.Network)),
                                                  dataFolder, walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), DateTimeProvider.Default, this.scriptAddressReader);
            var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, this.Network, this.standardTransactionPolicy);

            walletManager.Wallets.Add(wallet);

            var walletReference = new WalletAccountReference
            {
                AccountName = "account1",
                WalletName  = "myWallet1"
            };

            // create a trx with 3 outputs 50 + 50 + 50 = 150 BTC
            var context = new TransactionBuildContext(this.Network)
            {
                AccountReference = walletReference,
                MinConfirmations = 0,
                FeeType          = FeeType.Low,
                WalletPassword   = "******",
                Recipients       = new[]
                {
                    new Recipient {
                        Amount = new Money(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys1.PubKey.ScriptPubKey
                    },
                    new Recipient {
                        Amount = new Money(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys2.PubKey.ScriptPubKey
                    },
                    new Recipient {
                        Amount = new Money(50, MoneyUnit.BTC), ScriptPubKey = destinationKeys3.PubKey.ScriptPubKey
                    }
                }.ToList()
            };

            Transaction fundTransaction = walletTransactionHandler.BuildTransaction(context);

            Assert.Equal(4, fundTransaction.Inputs.Count);  // 4 inputs
            Assert.Equal(4, fundTransaction.Outputs.Count); // 3 outputs with change

            // remove the change output
            fundTransaction.Outputs.Remove(fundTransaction.Outputs.First(f => f.ScriptPubKey == context.ChangeAddress.ScriptPubKey));
            // remove 3 inputs they will be added back by fund transaction
            fundTransaction.Inputs.RemoveAt(3);
            fundTransaction.Inputs.RemoveAt(2);
            fundTransaction.Inputs.RemoveAt(1);
            Assert.Single(fundTransaction.Inputs); // 4 inputs

            Transaction fundTransactionClone = this.Network.CreateTransaction(fundTransaction.ToBytes());
            var         fundContext          = new TransactionBuildContext(this.Network)
            {
                AccountReference = walletReference,
                MinConfirmations = 0,
                FeeType          = FeeType.Low,
                WalletPassword   = "******",
                Recipients       = new List <Recipient>()
            };

            fundContext.OverrideFeeRate = overrideFeeRate;
            walletTransactionHandler.FundTransaction(fundContext, fundTransaction);

            foreach (TxIn input in fundTransactionClone.Inputs) // all original inputs are still in the trx
            {
                Assert.Contains(fundTransaction.Inputs, a => a.PrevOut == input.PrevOut);
            }

            Assert.Equal(4, fundTransaction.Inputs.Count);  // we expect 4 inputs
            Assert.Equal(4, fundTransaction.Outputs.Count); // we expect 4 outputs
            Assert.Equal(new Money(200, MoneyUnit.BTC) - fundContext.TransactionFee, fundTransaction.TotalOut);

            Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys1.PubKey.ScriptPubKey);
            Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys2.PubKey.ScriptPubKey);
            Assert.Contains(fundTransaction.Outputs, a => a.ScriptPubKey == destinationKeys3.PubKey.ScriptPubKey);
        }
        /// <summary>
        /// Creates a cold staking withdrawal <see cref="Transaction"/>.
        /// </summary>
        /// <remarks>
        /// Cold staking withdrawal is performed on the wallet that is in the role of the cold staking cold wallet.
        /// </remarks>
        /// <param name="walletTransactionHandler">The wallet transaction handler used to build the transaction.</param>
        /// <param name="receivingAddress">The address that will receive the withdrawal.</param>
        /// <param name="walletName">The name of the wallet in the role of cold wallet.</param>
        /// <param name="walletPassword">The wallet password.</param>
        /// <param name="amount">The amount to remove from cold staking.</param>
        /// <param name="feeAmount">The fee to pay for cold staking transaction withdrawal.</param>
        /// <returns>The <see cref="Transaction"/> for cold staking withdrawal.</returns>
        /// <exception cref="WalletException">Thrown if the receiving address is in a cold staking account in this wallet.</exception>
        /// <exception cref="ArgumentNullException">Thrown if the receiving address is invalid.</exception>
        internal Transaction GetColdStakingWithdrawalTransaction(IWalletTransactionHandler walletTransactionHandler, string receivingAddress,
                                                                 string walletName, string walletPassword, Money amount, Money feeAmount)
        {
            Guard.NotEmpty(receivingAddress, nameof(receivingAddress));
            Guard.NotEmpty(walletName, nameof(walletName));
            Guard.NotNull(amount, nameof(amount));
            Guard.NotNull(feeAmount, nameof(feeAmount));

            this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}:'{5}',{6}:'{7}'",
                                 nameof(receivingAddress), receivingAddress,
                                 nameof(walletName), walletName,
                                 nameof(amount), amount,
                                 nameof(feeAmount), feeAmount
                                 );

            Wallet.Wallet wallet = this.GetWalletByName(walletName);

            // Get the cold staking account.
            HdAccount coldAccount = this.GetColdStakingAccount(wallet, true);

            if (coldAccount == null)
            {
                this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_DOES_NOT_EXIST]");
                throw new WalletException("The cold wallet account does not exist.");
            }

            // Prevent reusing cold stake addresses as regular withdrawal addresses.
            if (coldAccount.ExternalAddresses.Concat(coldAccount.InternalAddresses).Select(a => a.Address.ToString()).Contains(receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_COLD_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking cold wallet account.");
            }

            HdAccount hotAccount = this.GetColdStakingAccount(wallet, false);

            if (hotAccount != null && hotAccount.ExternalAddresses.Concat(hotAccount.InternalAddresses).Select(a => a.Address.ToString()).Contains(receivingAddress))
            {
                this.logger.LogTrace("(-)[COLDSTAKE_INVALID_HOT_WALLET_ADDRESS_USAGE]");
                throw new WalletException("You can't send the money to a cold staking hot wallet account.");
            }

            // Send the money to the receiving address.
            Script destination = BitcoinAddress.Create(receivingAddress, wallet.Network).ScriptPubKey;

            // Create the transaction build context (used in BuildTransaction).
            var accountReference = new WalletAccountReference(walletName, coldAccount.Name);
            var context          = new TransactionBuildContext(wallet.Network)
            {
                AccountReference = accountReference,
                // Specify a dummy change address to prevent a change (internal) address from being created.
                // Will be changed after the transacton is built and before it is signed.
                ChangeAddress    = coldAccount.ExternalAddresses.First(),
                TransactionFee   = feeAmount,
                MinConfirmations = 0,
                Shuffle          = false,
                Recipients       = new[] { new Recipient {
                                               Amount = amount, ScriptPubKey = destination
                                           } }.ToList()
            };

            // Register the cold staking builder extension with the transaction builder.
            context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false));

            // Avoid script errors due to missing scriptSig.
            context.TransactionBuilder.StandardTransactionPolicy.ScriptVerify = null;

            // Build the transaction according to the settings recorded in the context.
            Transaction transaction = walletTransactionHandler.BuildTransaction(context);

            // Map OutPoint to UnspentOutputReference.
            Dictionary <OutPoint, UnspentOutputReference> mapOutPointToUnspentOutput = this.GetSpendableTransactionsInAccount(accountReference)
                                                                                       .ToDictionary(unspent => unspent.ToOutPoint(), unspent => unspent);

            // Set the cold staking scriptPubKey on the change output.
            TxOut changeOutput = transaction.Outputs.SingleOrDefault(output => (output.ScriptPubKey != destination) && (output.Value != 0));

            if (changeOutput != null)
            {
                // Find the largest input.
                TxIn largestInput = transaction.Inputs.OrderByDescending(input => mapOutPointToUnspentOutput[input.PrevOut].Transaction.Amount).Take(1).Single();

                // Set the scriptPubKey of the change output to the scriptPubKey of the largest input.
                changeOutput.ScriptPubKey = mapOutPointToUnspentOutput[largestInput.PrevOut].Transaction.ScriptPubKey;
            }

            // Add keys for signing inputs.
            foreach (TxIn input in transaction.Inputs)
            {
                UnspentOutputReference unspent = mapOutPointToUnspentOutput[input.PrevOut];
                context.TransactionBuilder.AddKeys(wallet.GetExtendedPrivateKeyForAddress(walletPassword, unspent.Address));
            }

            // Sign the transaction.
            context.TransactionBuilder.SignTransactionInPlace(transaction);

            this.logger.LogTrace("(-):'{0}'", transaction.GetHash());
            return(transaction);
        }
示例#29
0
        public void BuildTransactionFeeTooLowDefaultsToMinimumFee()
        {
            var walletFeePolicy = new Mock <IWalletFeePolicy>();

            walletFeePolicy.Setup(w => w.GetFeeRate(FeeType.Low.ToConfirmations()))
            .Returns(new FeeRate(0));

            Types.Wallet wallet = WalletTestsHelpers.GenerateBlankWallet("myWallet1", "password");
            (ExtKey ExtKey, string ExtPubKey)accountKeys = WalletTestsHelpers.GenerateAccountKeys(wallet, "password", "m/44'/0'/0'");
            (PubKey PubKey, BitcoinPubKeyAddress Address)spendingKeys    = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/0");
            (PubKey PubKey, BitcoinPubKeyAddress Address)destinationKeys = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "0/1");
            (PubKey PubKey, BitcoinPubKeyAddress Address)changeKeys      = WalletTestsHelpers.GenerateAddressKeys(wallet, accountKeys.ExtPubKey, "1/0");

            var address = new HdAddress
            {
                Index        = 0,
                HdPath       = $"m/44'/0'/0'/0/0",
                Address      = spendingKeys.Address.ToString(),
                Pubkey       = spendingKeys.PubKey.ScriptPubKey,
                ScriptPubKey = spendingKeys.Address.ScriptPubKey,
                //Transactions = new List<TransactionData>()
            };

            var chain = new ChainIndexer(wallet.Network);

            WalletTestsHelpers.AddBlocksWithCoinbaseToChain(wallet.walletStore as WalletMemoryStore, wallet.Network, chain, address);

            wallet.AccountsRoot.ElementAt(0).Accounts.Add(new HdAccount
            {
                Index             = 0,
                Name              = "account1",
                HdPath            = "m/44'/0'/0'",
                ExtendedPubKey    = accountKeys.ExtPubKey,
                ExternalAddresses = new List <HdAddress> {
                    address
                },
                InternalAddresses = new List <HdAddress>
                {
                    new HdAddress {
                        Index        = 0,
                        HdPath       = $"m/44'/0'/0'/1/0",
                        Address      = changeKeys.Address.ToString(),
                        Pubkey       = changeKeys.PubKey.ScriptPubKey,
                        ScriptPubKey = changeKeys.Address.ScriptPubKey,
                        //Transactions = new List<TransactionData>()
                    }
                }
            });

            string dataDir       = "TestData/WalletTransactionHandlerTest/BuildTransactionFeeTooLowThrowsWalletException";
            var    nodeSettings  = new NodeSettings(network: this.Network, args: new string[] { $"-datadir={dataDir}" });
            var    walletManager = new WalletManager(this.LoggerFactory.Object, this.Network, chain, new WalletSettings(nodeSettings),
                                                     new DataFolder(nodeSettings.DataDir), walletFeePolicy.Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), DateTimeProvider.Default, this.scriptAddressReader);
            var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, walletFeePolicy.Object, this.Network, this.standardTransactionPolicy);

            walletManager.Wallets.Add(wallet);

            var walletReference = new WalletAccountReference
            {
                AccountName = "account1",
                WalletName  = "myWallet1"
            };

            TransactionBuildContext context     = CreateContext(this.Network, walletReference, "password", destinationKeys.PubKey.ScriptPubKey, new Money(7500), FeeType.Low, 0);
            Transaction             transaction = walletTransactionHandler.BuildTransaction(context);

            Assert.Equal(new Money(this.Network.MinTxFee, MoneyUnit.Satoshi), context.TransactionFee);
        }
示例#30
0
        public void WalletCanMineWithColdWalletCoins()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                var network = new StratisRegTest();

                CoreNode stratisSender    = CreatePowPosMiningNode(builder, network, TestBase.CreateTestDir(this), coldStakeNode: false);
                CoreNode stratisHotStake  = CreatePowPosMiningNode(builder, network, TestBase.CreateTestDir(this), coldStakeNode: true);
                CoreNode stratisColdStake = CreatePowPosMiningNode(builder, network, TestBase.CreateTestDir(this), coldStakeNode: true);

                stratisSender.WithWallet().Start();
                stratisHotStake.WithWallet().Start();
                stratisColdStake.WithWallet().Start();

                var senderWalletManager = stratisSender.FullNode.WalletManager() as ColdStakingManager;
                var coldWalletManager   = stratisColdStake.FullNode.WalletManager() as ColdStakingManager;
                var hotWalletManager    = stratisHotStake.FullNode.WalletManager() as ColdStakingManager;

                // Set up cold staking account on cold wallet.
                coldWalletManager.GetOrCreateColdStakingAccount(WalletName, true, Password);
                HdAddress coldWalletAddress = coldWalletManager.GetFirstUnusedColdStakingAddress(WalletName, true);

                // Set up cold staking account on hot wallet.
                hotWalletManager.GetOrCreateColdStakingAccount(WalletName, false, Password);
                HdAddress hotWalletAddress = hotWalletManager.GetFirstUnusedColdStakingAddress(WalletName, false);

                int maturity = (int)stratisSender.FullNode.Network.Consensus.CoinbaseMaturity;
                TestHelper.MineBlocks(stratisSender, maturity + 16, true);

                // The mining should add coins to the wallet
                long total = stratisSender.FullNode.WalletManager().GetSpendableTransactionsInWallet(WalletName).Sum(s => s.Transaction.Amount);
                Assert.Equal(Money.COIN * 98000060, total);

                int confirmations = 10;

                var  walletAccountReference = new WalletAccountReference(WalletName, Account);
                long total2 = stratisSender.FullNode.WalletManager().GetSpendableTransactionsInAccount(walletAccountReference, confirmations).Sum(s => s.Transaction.Amount);

                // Sync all nodes
                TestHelper.ConnectAndSync(stratisHotStake, stratisSender);
                TestHelper.ConnectAndSync(stratisHotStake, stratisColdStake);
                TestHelper.Connect(stratisSender, stratisColdStake);

                // Send coins to hot wallet.
                Money     amountToSend = Money.COIN * 98000059;
                HdAddress sendto       = hotWalletManager.GetUnusedAddress(new WalletAccountReference(WalletName, Account));

                Transaction transaction1 = stratisSender.FullNode.WalletTransactionHandler().BuildTransaction(CreateContext(stratisSender.FullNode.Network, new WalletAccountReference(WalletName, Account), Password, sendto.ScriptPubKey, amountToSend, FeeType.Medium, confirmations));

                // Broadcast to the other node
                stratisSender.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(transaction1.ToHex()));

                // Wait for the transaction to arrive
                TestBase.WaitLoop(() => stratisHotStake.CreateRPCClient().GetRawMempool().Length > 0);
                Assert.NotNull(stratisHotStake.CreateRPCClient().GetRawTransaction(transaction1.GetHash(), null, false));
                TestBase.WaitLoop(() => stratisHotStake.FullNode.WalletManager().GetSpendableTransactionsInWallet(WalletName).Any());

                long receivetotal = stratisHotStake.FullNode.WalletManager().GetSpendableTransactionsInWallet(WalletName).Sum(s => s.Transaction.Amount);
                Assert.Equal(amountToSend, (Money)receivetotal);
                Assert.Null(stratisHotStake.FullNode.WalletManager().GetSpendableTransactionsInWallet(WalletName).First().Transaction.BlockHeight);

                // Setup cold staking from the hot wallet.
                Money       amountToSend2 = Money.COIN * 98000058;
                Transaction transaction2  = hotWalletManager.GetColdStakingSetupTransaction(stratisHotStake.FullNode.WalletTransactionHandler(),
                                                                                            coldWalletAddress.Address, hotWalletAddress.Address, WalletName, Account, Password, amountToSend2, new Money(0.02m, MoneyUnit.BTC));

                // Broadcast to the other node
                stratisHotStake.FullNode.NodeController <WalletController>().SendTransaction(new SendTransactionRequest(transaction2.ToHex()));

                // Wait for the transaction to arrive
                TestBase.WaitLoop(() => coldWalletManager.GetSpendableTransactionsInColdWallet(WalletName, true).Any());

                long receivetotal2 = coldWalletManager.GetSpendableTransactionsInColdWallet(WalletName, true).Sum(s => s.Transaction.Amount);
                Assert.Equal(amountToSend2, (Money)receivetotal2);
                Assert.Null(coldWalletManager.GetSpendableTransactionsInColdWallet(WalletName, true).First().Transaction.BlockHeight);

                // Allow coins to reach maturity
                TestHelper.MineBlocks(stratisSender, maturity, true);

                // Start staking.
                var hotMiningFeature = stratisHotStake.FullNode.NodeFeature <MiningFeature>();
                hotMiningFeature.StartStaking(WalletName, Password);

                TestBase.WaitLoop(() =>
                {
                    var stakingInfo = stratisHotStake.FullNode.NodeService <IPosMinting>().GetGetStakingInfoModel();
                    return(stakingInfo.Staking);
                });

                // Wait for new cold wallet transaction.
                var cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(3)).Token;
                TestBase.WaitLoop(() =>
                {
                    // Keep mining to ensure that staking outputs reach maturity.
                    TestHelper.MineBlocks(stratisSender, 1, true);
                    return(coldWalletAddress.Transactions.Count > 1);
                }, cancellationToken: cancellationToken);

                // Wait for money from staking.
                cancellationToken = new CancellationTokenSource(TimeSpan.FromMinutes(3)).Token;
                TestBase.WaitLoop(() =>
                {
                    // Keep mining to ensure that staking outputs reach maturity.
                    TestHelper.MineBlocks(stratisSender, 1, true);
                    return(coldWalletManager.GetSpendableTransactionsInColdWallet(WalletName, true).Sum(s => s.Transaction.Amount) > receivetotal2);
                }, cancellationToken: cancellationToken);
            }
        }