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