/// <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); }
public static HdAddress AddEmptyTransactionToAddress(HdAccount account, int addrType, int index) { HdAddress res; if (addrType == 0) { res = account.ExternalAddresses.Skip(index).First(); } else { res = account.InternalAddresses.Skip(index).First(); } res.Transactions.Add(new TransactionData() { Id = new uint256((ulong)addrType), Index = index, ScriptPubKey = res.ScriptPubKey }); return(res); }
/// <summary> /// Gets a cold staking account. /// </summary> /// <remarks> /// <para>In order to keep track of cold staking addresses and balances we are using <see cref="HdAccount"/>'s /// with indexes starting from the value defined in <see cref="Wallet.Wallet.SpecialPurposeAccountIndexesStart"/>. /// </para><para> /// We are using two such accounts, one when the wallet is in the role of cold wallet, and another one when /// the wallet is in the role of hot wallet. For this reason we specify the required account when calling this /// method. /// </para></remarks> /// <param name="wallet">The wallet where we wish to create the account.</param> /// <param name="isColdWalletAccount">Indicates whether we need the cold wallet account (versus the hot wallet account).</param> /// <returns>The cold staking account or <c>null</c> if the account does not exist.</returns> internal HdAccount GetColdStakingAccount(Wallet.Wallet wallet, bool isColdWalletAccount) { this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(wallet), wallet.Name, nameof(isColdWalletAccount), isColdWalletAccount); var coinType = (CoinType)wallet.Network.Consensus.CoinType; HdAccount account = null; try { account = wallet.GetAccountByCoinType(isColdWalletAccount ? ColdWalletAccountName : HotWalletAccountName, coinType); } catch (Exception) { } if (account == null) { this.logger.LogTrace("(-)[ACCOUNT_DOES_NOT_EXIST]:null"); return(null); } this.logger.LogTrace("(-):'{0}'", account.Name); return(account); }
public void Given_GetMaximumSpendableAmountIsCalledForConfirmedTransactions_When_ThereAreNoConfirmedSpendableFound_Then_MaxAmountReturnsAsZero() { DataFolder dataFolder = CreateDataFolder(this); var walletManager = new WalletManager(this.LoggerFactory.Object, Network.Main, new ConcurrentChain(Network.Main), NodeSettings.Default(), new Mock <WalletSettings>().Object, dataFolder, new Mock <IWalletFeePolicy>().Object, new Mock <IAsyncLoopFactory>().Object, new NodeLifetime(), DateTimeProvider.Default); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, It.IsAny <WalletFeePolicy>(), Network.Main); HdAccount account = WalletTestsHelpers.CreateAccount("account 1"); HdAddress accountAddress1 = WalletTestsHelpers.CreateAddress(); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(1), new Money(15000), null)); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(2), new Money(10000), null)); HdAddress accountAddress2 = WalletTestsHelpers.CreateAddress(); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(3), new Money(20000), null)); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(4), new Money(120000), null)); account.ExternalAddresses.Add(accountAddress1); account.InternalAddresses.Add(accountAddress2); var wallet = WalletTestsHelpers.CreateWallet("wallet1"); wallet.AccountsRoot.Add(new AccountRoot() { Accounts = new List <HdAccount> { account } }); walletManager.Wallets.Add(wallet); (Money max, Money fee)result = walletTransactionHandler.GetMaximumSpendableAmount(new WalletAccountReference("wallet1", "account 1"), FeeType.Low, false); Assert.Equal(Money.Zero, result.max); Assert.Equal(Money.Zero, result.fee); }
public void Given_GetMaximumSpendableAmountIsCalled_When_ThereAreNoSpendableFound_Then_MaxAmountReturnsAsZero() { DataFolder dataFolder = CreateDataFolder(this); var walletManager = new WalletManager(this.LoggerFactory.Object, this.Network, new ChainIndexer(this.Network), new WalletSettings(NodeSettings.Default(this.Network)), dataFolder, new Mock <IWalletFeePolicy>().Object, new Mock <IAsyncProvider>().Object, new NodeLifetime(), DateTimeProvider.Default, this.scriptAddressReader); var walletTransactionHandler = new WalletTransactionHandler(this.LoggerFactory.Object, walletManager, It.IsAny <WalletFeePolicy>(), this.Network, this.standardTransactionPolicy); HdAccount account = WalletTestsHelpers.CreateAccount("account 1"); HdAddress accountAddress1 = WalletTestsHelpers.CreateAddress(); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(1), new Money(15000), 1, new SpendingDetails())); accountAddress1.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(2), new Money(10000), 1, new SpendingDetails())); HdAddress accountAddress2 = WalletTestsHelpers.CreateAddress(); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(3), new Money(20000), 3, new SpendingDetails())); accountAddress2.Transactions.Add(WalletTestsHelpers.CreateTransaction(new uint256(4), new Money(120000), 4, new SpendingDetails())); account.ExternalAddresses.Add(accountAddress1); account.InternalAddresses.Add(accountAddress2); Wallet wallet = WalletTestsHelpers.CreateWallet("wallet1"); wallet.AccountsRoot.Add(new AccountRoot() { Accounts = new List <HdAccount> { account } }); walletManager.Wallets.Add(wallet); (Money max, Money fee)result = walletTransactionHandler.GetMaximumSpendableAmount(new WalletAccountReference("wallet1", "account 1"), FeeType.Low, true); Assert.Equal(Money.Zero, result.max); Assert.Equal(Money.Zero, result.fee); }
/// <summary> /// Gets the balance of a wallet. /// </summary> /// <param name="request">The request parameters.</param> /// <returns></returns> public WalletBalanceModel GetBalance(WalletBalanceRequest request) { Guard.NotNull(request, nameof(request)); // checks the request is valid //if (!this.ModelState.IsValid) //{ // return BuildErrorResponse(this.ModelState); //} try { WalletBalanceModel model = new WalletBalanceModel(); IEnumerable <AccountBalance> balances = this.walletManager.GetBalances(request.WalletName, request.AccountName); foreach (AccountBalance balance in balances) { HdAccount account = balance.Account; model.AccountsBalances.Add(new AccountBalanceModel { CoinType = this.coinType, Name = account.Name, HdPath = account.HdPath, AmountConfirmed = balance.AmountConfirmed, AmountUnconfirmed = balance.AmountUnconfirmed }); } return(model); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); //return ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()); throw; } }
public void GetFirstUnusedReceivingAddressWithoutExistingUnusedReceivingAddressReturnsNull() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress() { Index = 2, Transactions = new List <TransactionData>() { new TransactionData() } }); account.ExternalAddresses.Add(new HdAddress() { Index = 1, Transactions = new List <TransactionData>() { new TransactionData() } }); var result = account.GetFirstUnusedReceivingAddress(); Assert.Null(result); }
public void GetSpendableTransactionsWithoutSpendableTransactionsReturnsEmptyList() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 7, SpendingDetails = new SpendingDetails() } } }); account.InternalAddresses.Add(new HdAddress { Index = 4, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 10, SpendingDetails = new SpendingDetails() } } }); IEnumerable <UnspentOutputReference> result = account.GetSpendableTransactions(100, 10, 0); Assert.Empty(result); }
public static void SetMinerSecret(CoreNode coreNode, string walletName = "mywallet", string walletPassword = "******", string accountName = "account 0", string miningAddress = null) { if (coreNode.MinerSecret == null) { Wallet wallet = coreNode.FullNode.WalletManager().GetWallet(walletName); HdAccount account = wallet.GetAccount(accountName); HdAddress address; if (!string.IsNullOrEmpty(miningAddress)) { address = account.ExternalAddresses.Concat(account.InternalAddresses).Single(add => add.Address == miningAddress); } else { address = account.GetFirstUnusedReceivingAddress(); } coreNode.MinerHDAddress = address; Key extendedPrivateKey = wallet.GetExtendedPrivateKeyForAddress(walletPassword, address).PrivateKey; coreNode.SetMinerSecret(new BitcoinSecret(extendedPrivateKey, coreNode.FullNode.Network)); } }
public void GetFirstUnusedReceivingAddressWithExistingUnusedReceivingAddressReturnsAddressWithLowestIndex() { var store = new WalletMemoryStore(); var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 3, Address = "3" }); account.ExternalAddresses.Add(new HdAddress { Index = 2, Address = "2" }); account.ExternalAddresses.Add(new HdAddress { Index = 1, Address = "1" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(1), 1), Address = "1" } }); HdAddress result = account.GetFirstUnusedReceivingAddress(store); Assert.Equal(account.ExternalAddresses.ElementAt(1), result); }
public void GetTransactionsByIdHavingNoMatchingTransactionsReturnsEmptyList() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 7 } } }); account.InternalAddresses.Add(new HdAddress { Index = 4, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 10 } } }); IEnumerable <TransactionData> result = account.GetTransactionsById(new uint256(20)); Assert.Empty(result); }
/// <summary> /// Gets the first account from the "default" wallet if it specified, /// otherwise returns the first available account in the existing wallets. /// </summary> /// <returns>Reference to the default wallet account, or the first available if no default wallet is specified.</returns> private WalletAccountReference GetWalletAccountReference() { string walletName = null; if (this.walletSettings.IsDefaultWalletEnabled()) { walletName = this.walletManager.GetWalletsNames().FirstOrDefault(w => w == this.walletSettings.DefaultWalletName); } else { //TODO: Support multi wallet like core by mapping passed RPC credentials to a wallet/account walletName = this.walletManager.GetWalletsNames().FirstOrDefault(); } if (walletName == null) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "No wallet found"); } HdAccount account = this.walletManager.GetAccounts(walletName).First(); return(new WalletAccountReference(walletName, account.Name)); }
/// <summary> /// Finds first available wallet and its account. /// </summary> /// <returns>Reference to wallet account.</returns> private WalletAccountReference GetAccount() { this.logger.LogTrace("()"); string walletName = this.walletManager.GetWalletsNames().FirstOrDefault(); if (walletName == null) { throw new RPCServerException(NBitcoin.RPC.RPCErrorCode.RPC_INVALID_REQUEST, "No wallet found"); } HdAccount account = this.walletManager.GetAccounts(walletName).FirstOrDefault(); if (account == null) { throw new RPCServerException(NBitcoin.RPC.RPCErrorCode.RPC_INVALID_REQUEST, "No account found on wallet"); } var res = new WalletAccountReference(walletName, account.Name); this.logger.LogTrace("(-):'{0}'", res); return(res); }
public void GetLastUsedAddressWithReceivingAddressesHavingTransactionsReturnsHighestIndex() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List <TransactionData> { new TransactionData() } }); account.ExternalAddresses.Add(new HdAddress { Index = 3, Transactions = new List <TransactionData> { new TransactionData() } }); account.ExternalAddresses.Add(new HdAddress { Index = 1, Transactions = new List <TransactionData> { new TransactionData() } }); HdAddress result = account.GetLastUsedAddress(isChange: false); Assert.Equal(account.ExternalAddresses.ElementAt(1), result); }
public void GetFirstUnusedReceivingAddressWithExistingUnusedReceivingAddressReturnsAddressWithLowestIndex() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress() { Index = 3 }); account.ExternalAddresses.Add(new HdAddress() { Index = 2 }); account.ExternalAddresses.Add(new HdAddress() { Index = 1, Transactions = new List <TransactionData>() { new TransactionData() } }); var result = account.GetFirstUnusedReceivingAddress(); Assert.Equal(account.ExternalAddresses.ElementAt(1), result); }
public void GetFirstUnusedChangeAddressWithoutExistingUnusedChangeAddressReturnsNull() { var store = new WalletMemoryStore(); var account = new HdAccount(); account.InternalAddresses.Add(new HdAddress { Index = 2, Address = "2" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(1), 1), Address = "2" } }); account.InternalAddresses.Add(new HdAddress { Index = 1, Address = "1" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(2), 1), Address = "1" } }); HdAddress result = account.GetFirstUnusedChangeAddress(store); Assert.Null(result); }
/// <summary> /// Creates a cold staking account and ensures that it has at least one address. /// If the account already exists then the existing account is returned. /// </summary> /// <remarks> /// <para>In order to keep track of cold staking addresses and balances we are using <see cref="HdAccount"/>'s /// with indexes starting from the value defined in <see cref="Wallet.Wallet.SpecialPurposeAccountIndexesStart"/>. /// </para><para> /// We are using two such accounts, one when the wallet is in the role of cold wallet, and another one when /// the wallet is in the role of hot wallet. For this reason we specify the required account when calling this /// method. /// </para></remarks> /// <param name="walletName">The name of the wallet where we wish to create the account.</param> /// <param name="isColdWalletAccount">Indicates whether we need the cold wallet account (versus the hot wallet account).</param> /// <param name="walletPassword">The wallet password which will be used to create the account.</param> /// <returns>The new or existing cold staking account.</returns> internal HdAccount GetOrCreateColdStakingAccount(string walletName, bool isColdWalletAccount, string walletPassword) { this.logger.LogTrace("({0}:'{1}',{2}:{3})", nameof(walletName), walletName, nameof(isColdWalletAccount), isColdWalletAccount); Wallet.Wallet wallet = this.GetWalletByName(walletName); HdAccount account = this.GetColdStakingAccount(wallet, isColdWalletAccount); if (account != null) { this.logger.LogTrace("(-)[ACCOUNT_ALREADY_EXIST]:'{0}'", account.Name); return(account); } int accountIndex = isColdWalletAccount ? ColdWalletAccountIndex : HotWalletAccountIndex; var coinType = (CoinType)wallet.Network.Consensus.CoinType; this.logger.LogTrace("The {0} wallet account for '{1}' does not exist and will now be created.", isColdWalletAccount ? "cold" : "hot", wallet.Name); AccountRoot accountRoot = wallet.AccountsRoot.Single(a => a.CoinType == coinType); account = accountRoot.CreateAccount(walletPassword, wallet.EncryptedSeed, wallet.ChainCode, wallet.Network, this.dateTimeProvider.GetTimeOffset(), accountIndex, isColdWalletAccount ? ColdWalletAccountName : HotWalletAccountName); // Maintain at least one unused address at all times. This will ensure that wallet recovery will also work. account.CreateAddresses(wallet.Network, 1, false); ICollection <HdAccount> hdAccounts = accountRoot.Accounts.ToList(); hdAccounts.Add(account); accountRoot.Accounts = hdAccounts; this.logger.LogTrace("(-):'{0}'", account.Name); return(account); }
public void GetSpendableTransactionsWithoutSpendableTransactionsReturnsEmptyList() { var store = new WalletMemoryStore(); var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Address = "2" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(1), 1), Address = "2", Id = new uint256(15), Index = 7, SpendingDetails = new SpendingDetails() } }); account.InternalAddresses.Add(new HdAddress { Index = 4 }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(2), 1), Address = "4", Id = new uint256(15), Index = 10, SpendingDetails = new SpendingDetails() } }); IEnumerable <UnspentOutputReference> result = account.GetSpendableTransactions(store, 100, 10, 0); Assert.Empty(result); }
public void GetTransactionsByIdHavingTransactionsWithIdReturnsTransactions() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 7 } } }); account.ExternalAddresses.Add(new HdAddress { Index = 3, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(18), Index = 8 } } }); account.ExternalAddresses.Add(new HdAddress { Index = 1, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(19), Index = 9 } } }); account.ExternalAddresses.Add(new HdAddress { Index = 6, Transactions = null }); account.InternalAddresses.Add(new HdAddress { Index = 4, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 10 } } }); account.InternalAddresses.Add(new HdAddress { Index = 5, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(18), Index = 11 } } }); account.InternalAddresses.Add(new HdAddress { Index = 6, Transactions = null }); account.InternalAddresses.Add(new HdAddress { Index = 6, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(19), Index = 12 } } }); IEnumerable <TransactionData> result = account.GetTransactionsById(new uint256(18)); Assert.Equal(2, result.Count()); Assert.Equal(8, result.ElementAt(0).Index); Assert.Equal(new uint256(18), result.ElementAt(0).Id); Assert.Equal(11, result.ElementAt(1).Index); Assert.Equal(new uint256(18), result.ElementAt(1).Id); }
public void TestMultiClientWithoutTor(int numClients) { // Workaround for segwit not correctly activating Network.RegTest.Consensus.BIP9Deployments[BIP9Deployments.Segwit] = new BIP9DeploymentsParameters(1, 0, DateTime.Now.AddDays(50).ToUnixTimestamp()); NodeBuilder builder = NodeBuilder.Create(version: "0.15.1"); CoreNode coreNode = GetCoreNode(builder); coreNode.Start(); // Replicate portions of BreezeServer's Program.cs. Maybe refactor it into a class/function in future var serviceProvider = new ServiceCollection() .AddLogging() .AddSingleton <Breeze.BreezeServer.Services.ITumblerService, Breeze.BreezeServer.Services.TumblerService>() .BuildServiceProvider(); serviceProvider .GetService <ILoggerFactory>() .AddConsole(LogLevel.Debug); // Skip the registration code - that can be tested separately string configPath = Path.Combine(coreNode.DataFolder, "breeze.conf"); File.WriteAllLines(configPath, this.breezeServerConfig); BreezeConfiguration config = new BreezeConfiguration(configPath); var coreRpc = coreNode.CreateRPCClient(); string ntbServerConfigPath = Path.Combine(coreNode.DataFolder, "server.config"); File.WriteAllLines(ntbServerConfigPath, GetNTBServerConfig(coreRpc)); // We need to start up the masternode prior to creating the SBFN instance so that // we have the URI available for starting the TumbleBit feature // TODO: Also need to see if NTB interactive console interferes with later parts of the test new Thread(delegate() { Thread.CurrentThread.IsBackground = true; // By instantiating the TumblerService directly the registration logic is skipped var tumbler = serviceProvider.GetService <Breeze.BreezeServer.Services.ITumblerService>(); tumbler.StartTumbler(config, false, "server.config", Path.GetFullPath(coreNode.DataFolder), false); }).Start(); // Wait for URI file to be written out by the TumblerService while (!File.Exists(Path.Combine(coreNode.DataFolder, "uri.txt"))) { Thread.Sleep(1000); } Console.WriteLine("* URI file detected *"); Thread.Sleep(5000); var serverAddress = File.ReadAllText(Path.Combine(coreNode.DataFolder, "uri.txt")); // Not used for this test ConfigurationOptionWrapper <object> registrationStoreDirectory = new ConfigurationOptionWrapper <object>("RegistrationStoreDirectory", ""); // Force SBFN to use the temporary hidden service to connect to the server ConfigurationOptionWrapper <object> masternodeUri = new ConfigurationOptionWrapper <object>("MasterNodeUri", serverAddress); ConfigurationOptionWrapper <object>[] configurationOptions = { registrationStoreDirectory, masternodeUri }; List <CoreNode> clientNodes = new List <CoreNode>(); int apiPortNum = 37229; for (int i = 0; i < numClients; i++) { var temp = builder.CreateStratisPowNode(false, fullNodeBuilder => { fullNodeBuilder .UsePowConsensus() .UseBlockStore() .UseMempool() .UseBlockNotification() .UseTransactionNotification() .AddMining() .UseWallet() .UseWatchOnlyWallet() .UseApi() .AddRPC() .UseTumbleBit(configurationOptions); }); temp.ConfigParameters.AddOrReplace("apiuri", $"http://localhost:{apiPortNum}"); clientNodes.Add(temp); apiPortNum++; } foreach (var node in clientNodes) { node.Start(); } // Create the source and destination wallets for nodes for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; wm1.CreateWallet("TumbleBit1", $"alice{i}"); wm1.CreateWallet("TumbleBit1", $"bob{i}"); } // Mined coins only mature after 100 blocks on regtest // Additionally, we need to force Segwit to activate in order for NTB to work correctly coreRpc.Generate(450); while (coreRpc.GetBlockCount() < 450) { Thread.Sleep(100); } for (int i = 0; i < numClients; i++) { coreRpc.AddNode(clientNodes[i].Endpoint, false); var rpc = clientNodes[i].CreateRPCClient(); rpc.AddNode(coreNode.Endpoint, false); for (int j = 0; j < numClients; j++) { if (i != j) { rpc.AddNode(clientNodes[j].Endpoint, false); } } } for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; var destination1 = wm1.GetUnusedAddress(new WalletAccountReference($"alice{i}", "account 0")); coreRpc.SendToAddress(BitcoinAddress.Create(destination1.Address, Network.RegTest), new Money(5.0m, MoneyUnit.BTC)); } clientNodes[0].FullNode.Settings.Logger.LogInformation("Waiting for transactions to propagate and finalise"); Thread.Sleep(5000); coreRpc.Generate(1); // Wait for SBFN clients to sync with the core node foreach (var node in clientNodes) { TestHelper.WaitLoop(() => node.CreateRPCClient().GetBestBlockHash() == coreRpc.GetBestBlockHash()); } // Test implementation note: the coins do not seem to immediately appear in the wallet. // This is possibly some sort of race condition between the wallet manager and block generation/sync. // This extra delay seems to ensure that the coins are definitely in the wallet by the time the // transaction count gets logged to the console below. // Wait instead of generating a block Thread.Sleep(5000); for (int i = 0; i < numClients; i++) { var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; //logger1.LogError($"({i}) Number of wallet transactions: " + wm1.GetSpendableTransactionsInWallet($"alice{i}").Count()); // Connect each client to server and start tumbling using (HttpClient client = new HttpClient()) { client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var apiSettings1 = clientNodes[i].FullNode.NodeService <ApiSettings>(); var connectContent = new StringContent(new ConnectRequest { OriginWalletName = $"alice{i}" }.ToString(), Encoding.UTF8, "application/json"); var connectResponse = client.PostAsync(apiSettings1.ApiUri + "api/TumbleBit/connect", connectContent).GetAwaiter().GetResult(); var tumbleContent = new StringContent(new TumbleRequest { OriginWalletName = $"alice{i}", OriginWalletPassword = "******", DestinationWalletName = $"bob{i}" }.ToString(), Encoding.UTF8, "application/json"); var tumbleResponse = client.PostAsync(apiSettings1.ApiUri + "api/TumbleBit/tumble", tumbleContent).GetAwaiter().GetResult(); // Note that the TB client takes about 30 seconds to completely start up, as it has to check the server parameters and RSA key proofs } clientNodes[i].FullNode.Settings.Logger.LogInformation($"Client ({i}) About to start tumbling loop"); } while (true) { for (int i = 0; i < numClients; i++) { clientNodes[i].FullNode.Settings.Logger.LogInformation($"Wallet {i} balance height: " + clientNodes[i].FullNode.Chain.Height); var wm1 = clientNodes[i].FullNode.NodeService <IWalletManager>() as WalletManager; HdAccount alice1 = wm1.GetWalletByName($"alice{i}").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(A{i}) Confirmed: " + alice1.GetSpendableAmount().ConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(A{i}) Unconfirmed: " + alice1.GetSpendableAmount().UnConfirmedAmount.ToString()); HdAccount bob1 = wm1.GetWalletByName($"bob{i}").GetAccountByCoinType("account 0", (CoinType)Network.RegTest.Consensus.CoinType); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(B{i}) Confirmed: " + bob1.GetSpendableAmount().ConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation($"(B{i}) Unconfirmed: " + bob1.GetSpendableAmount().UnConfirmedAmount.ToString()); clientNodes[i].FullNode.Settings.Logger.LogInformation("==="); } coreRpc.Generate(1); // Try to ensure the invalid phase error does not occur // (seems to occur when the server has not yet processed a new block and the client has) //TestHelper.WaitLoop(() => rpc1.GetBestBlockHash() == coreRpc.GetBestBlockHash()); //TestHelper.WaitLoop(() => rpc2.GetBestBlockHash() == coreRpc.GetBestBlockHash()); /*var mempool = node1.FullNode.NodeService<MempoolManager>(); * var mempoolTx = mempool.GetMempoolAsync().Result; * if (mempoolTx.Count > 0) * { * Console.WriteLine("--- Mempool contents ---"); * foreach (var tx in mempoolTx) * { * var hex = mempool.GetTransaction(tx).Result; * Console.WriteLine(tx + " ->"); * Console.WriteLine(hex); * Console.WriteLine("---"); * } * }*/ Thread.Sleep(20000); } if (builder != null) { builder.Dispose(); } }
public void BuildTransferContextCorrectly() { const int utxoIndex = 0; uint256 utxoId = uint256.Zero; uint256 utxoIdUnused = uint256.One; string senderAddress = uint160.Zero.ToBase58Address(this.network); string recipientAddress = uint160.One.ToBase58Address(this.network); string changeAddress = new uint160(2).ToBase58Address(this.network); var amount = 1234.567M; var request = new BuildContractTransactionRequest { AccountName = "account 0", FeeAmount = "0.01", WalletName = "wallet", Password = "******", Sender = senderAddress, ShuffleOutputs = true, AllowUnconfirmed = true, ChangeAddress = changeAddress, Recipients = new List <RecipientModel> { // In locales that use a , for the decimal point this would fail to be parsed unless we use the invariant culture new RecipientModel { Amount = amount.ToString(CultureInfo.InvariantCulture), DestinationAddress = recipientAddress } } }; var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object); var service = new SmartContractTransactionService( this.network, this.walletManager.Object, this.walletTransactionHandler.Object, this.stringSerializer.Object, this.callDataSerializer.Object, this.addressGenerator.Object, this.stateRepository.Object, reserveUtxoService); var senderHdAddress = new HdAddress { Address = senderAddress }; var wallet = new Features.Wallet.Wallet(); wallet.AccountsRoot.Add(new AccountRoot(wallet)); var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts) { Name = request.AccountName }; account0.ExternalAddresses.Add(new HdAddress() { Address = senderAddress }); this.walletManager.Setup(x => x.GetWallet(request.WalletName)) .Returns(wallet); this.walletManager.Setup(x => x.GetAddressBalance(request.Sender)) .Returns(new AddressBalance { Address = request.Sender, AmountConfirmed = Money.FromUnit(amount, MoneyUnit.BTC), AmountUnconfirmed = 0 }); var outputs = new List <UnspentOutputReference> { new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoId, Index = utxoIndex, } }, new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoIdUnused, Index = utxoIndex, } } }; this.walletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <string>(), 0)).Returns(outputs); this.walletTransactionHandler.Setup(x => x.BuildTransaction(It.IsAny <TransactionBuildContext>())) .Returns(new Transaction()); BuildContractTransactionResult result = service.BuildTx(request); // Check that the transaction builder is invoked, and that we: // - Ignore shuffleOutputs, // - Set inputs from sender // - Set recipients, // - Set change to sender this.walletTransactionHandler.Verify(w => w.BuildTransaction(It.Is <TransactionBuildContext>(context => context.AllowOtherInputs == false && context.Shuffle == false && context.SelectedInputs.All(i => outputs.Select(o => o.Transaction.Id).Contains(i.Hash)) && context.Recipients.Single().Amount == Money.FromUnit(amount, MoneyUnit.BTC) && context.ChangeAddress.Address == senderHdAddress.Address ))); }
public void BuildTransferContext_RecipientIsKnownContract_Fails() { const int utxoIndex = 0; uint256 utxoId = uint256.Zero; uint256 utxoIdUnused = uint256.One; string senderAddress = uint160.Zero.ToBase58Address(this.network); string recipientAddress = uint160.One.ToBase58Address(this.network); var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object); var service = new SmartContractTransactionService( this.network, this.walletManager.Object, this.walletTransactionHandler.Object, this.stringSerializer.Object, this.callDataSerializer.Object, this.addressGenerator.Object, this.stateRepository.Object, reserveUtxoService); var request = new BuildContractTransactionRequest { AccountName = "account 0", WalletName = "wallet", Password = "******", Sender = senderAddress, Recipients = new List <RecipientModel> { new RecipientModel { Amount = "1", DestinationAddress = recipientAddress } } }; var wallet = new Features.Wallet.Wallet(); wallet.AccountsRoot.Add(new AccountRoot(wallet)); var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts) { Name = request.AccountName }; account0.ExternalAddresses.Add(new HdAddress() { Address = senderAddress }); this.walletManager.Setup(x => x.GetWallet(request.WalletName)) .Returns(wallet); this.walletManager.Setup(x => x.GetAddressBalance(request.Sender)) .Returns(new AddressBalance { Address = request.Sender, AmountConfirmed = 10, AmountUnconfirmed = 0 }); this.walletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <string>(), 0)) .Returns(new List <UnspentOutputReference> { new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoId, Index = utxoIndex, } }, new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoIdUnused, Index = utxoIndex, } } }); this.stateRepository.Setup(s => s.IsExist(It.IsAny <uint160>())).Returns(true); BuildContractTransactionResult result = service.BuildTx(request); Assert.Equal(SmartContractTransactionService.TransferFundsToContractError, result.Error); Assert.NotNull(result.Message); Assert.Null(result.Response); }
public BuildCallContractTransactionResponse BuildCallTx(BuildCallContractTransactionRequest request) { AddressBalance addressBalance = this.walletManager.GetAddressBalance(request.Sender); if (addressBalance.AmountConfirmed == 0 && addressBalance.AmountUnconfirmed == 0) { return(BuildCallContractTransactionResponse.Failed(SenderNoBalanceError)); } var selectedInputs = new List <OutPoint>(); selectedInputs = this.walletManager.GetSpendableInputsForAddress(request.WalletName, request.Sender); uint160 addressNumeric = request.ContractAddress.ToUint160(this.network); 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, addressNumeric, request.MethodName, methodParameters); } catch (MethodParameterStringSerializerException exception) { return(BuildCallContractTransactionResponse.Failed(exception.Message)); } } else { txData = new ContractTxData(ReflectionVirtualMachine.VmVersion, (Gas)request.GasPrice, (Gas)request.GasLimit, addressNumeric, request.MethodName); } 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(BuildCallContractTransactionResponse.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 context = new TransactionBuildContext(this.network) { AccountReference = new WalletAccountReference(request.WalletName, request.AccountName), TransactionFee = totalFee, ChangeAddress = senderAddress, SelectedInputs = selectedInputs, MinConfirmations = MinConfirmationsAllChecks, WalletPassword = request.Password, Recipients = new[] { new Recipient { Amount = request.Amount, ScriptPubKey = new Script(this.callDataSerializer.Serialize(txData)) } }.ToList() }; try { Transaction transaction = this.walletTransactionHandler.BuildTransaction(context); return(BuildCallContractTransactionResponse.Succeeded(request.MethodName, transaction, context.TransactionFee)); } catch (Exception exception) { return(BuildCallContractTransactionResponse.Failed(exception.Message)); } }
public void GetSpendableTransactionsWithSpendableTransactionsReturnsSpendableTransactions() { var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 7, SpendingDetails = new SpendingDetails() } } }); account.ExternalAddresses.Add(new HdAddress { Index = 3, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(18), Index = 8 } } }); account.ExternalAddresses.Add(new HdAddress { Index = 1, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(19), Index = 9, SpendingDetails = new SpendingDetails() } } }); account.ExternalAddresses.Add(new HdAddress { Index = 6, Transactions = null }); account.InternalAddresses.Add(new HdAddress { Index = 4, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(15), Index = 10, SpendingDetails = new SpendingDetails() } } }); account.InternalAddresses.Add(new HdAddress { Index = 5, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(18), Index = 11 } } }); account.InternalAddresses.Add(new HdAddress { Index = 6, Transactions = null }); account.InternalAddresses.Add(new HdAddress { Index = 6, Transactions = new List <TransactionData> { new TransactionData { Id = new uint256(19), Index = 12, SpendingDetails = new SpendingDetails() } } }); IEnumerable <UnspentOutputReference> result = account.GetSpendableTransactions(100, 10, 0); Assert.Equal(2, result.Count()); Assert.Equal(8, result.ElementAt(0).Transaction.Index); Assert.Equal(new uint256(18), result.ElementAt(0).Transaction.Id); Assert.Equal(11, result.ElementAt(1).Transaction.Index); Assert.Equal(new uint256(18), result.ElementAt(1).Transaction.Id); }
/// <summary> /// Creates cold staking setup <see cref="Transaction"/>. /// </summary> /// <remarks> /// The <paramref name="coldWalletAddress"/> and <paramref name="hotWalletAddress"/> would be expected to be /// from different wallets and typically also different physical machines under normal circumstances. The following /// rules are enforced by this method and would lead to a <see cref="WalletException"/> otherwise: /// <list type="bullet"> /// <item><description>The cold and hot wallet addresses are expected to belong to different wallets.</description></item> /// <item><description>Either the cold or hot wallet address must belong to a cold staking account in the wallet identified /// by <paramref name="walletName"/></description></item> /// <item><description>The account specified in <paramref name="walletAccount"/> can't be a cold staking account.</description></item> /// </list> /// </remarks> /// <param name="walletTransactionHandler">The wallet transaction handler. Contains the <see cref="WalletTransactionHandler.BuildTransaction"/> method.</param> /// <param name="coldWalletAddress">The cold wallet address generated by <see cref="GetColdStakingAddress"/>.</param> /// <param name="hotWalletAddress">The hot wallet address generated by <see cref="GetColdStakingAddress"/>.</param> /// <param name="walletName">The name of the wallet.</param> /// <param name="walletAccount">The wallet account.</param> /// <param name="walletPassword">The wallet password.</param> /// <param name="amount">The amount to cold stake.</param> /// <param name="feeAmount">The fee to pay for the cold staking setup transaction.</param> /// <returns>The <see cref="Transaction"/> for setting up cold staking.</returns> /// <exception cref="WalletException">Thrown if any of the rules listed in the remarks section of this method are broken.</exception> internal Transaction GetColdStakingSetupTransaction(IWalletTransactionHandler walletTransactionHandler, string coldWalletAddress, string hotWalletAddress, string walletName, string walletAccount, string walletPassword, Money amount, Money feeAmount) { Guard.NotNull(walletTransactionHandler, nameof(walletTransactionHandler)); Guard.NotEmpty(coldWalletAddress, nameof(coldWalletAddress)); Guard.NotEmpty(hotWalletAddress, nameof(hotWalletAddress)); Guard.NotEmpty(walletName, nameof(walletName)); Guard.NotEmpty(walletAccount, nameof(walletAccount)); Guard.NotNull(amount, nameof(amount)); Guard.NotNull(feeAmount, nameof(feeAmount)); this.logger.LogTrace("({0}:'{1}',{2}:'{3}',{4}:'{5}',{6}:'{7}',{8}:{9},{10}:{11})", nameof(coldWalletAddress), coldWalletAddress, nameof(hotWalletAddress), hotWalletAddress, nameof(walletName), walletName, nameof(walletAccount), walletAccount, nameof(amount), amount, nameof(feeAmount), feeAmount ); Wallet.Wallet wallet = this.GetWalletByName(walletName); // Get/create the cold staking accounts. HdAccount coldAccount = this.GetOrCreateColdStakingAccount(walletName, true, walletPassword); HdAccount hotAccount = this.GetOrCreateColdStakingAccount(walletName, false, walletPassword); bool thisIsColdWallet = coldAccount?.ExternalAddresses.Select(a => a.Address).Contains(coldWalletAddress) ?? false; bool thisIsHotWallet = hotAccount?.ExternalAddresses.Select(a => a.Address).Contains(hotWalletAddress) ?? false; this.logger.LogTrace("Local wallet '{0}' does{1} contain cold wallet address '{2}' and does{3} contain hot wallet address '{4}'.", walletName, thisIsColdWallet ? "" : " NOT", coldWalletAddress, thisIsHotWallet ? "" : " NOT", hotWalletAddress); if (thisIsColdWallet && thisIsHotWallet) { this.logger.LogTrace("(-)[COLDSTAKE_BOTH_HOT_AND_COLD]"); throw new WalletException("You can't use this wallet as both hot wallet and cold wallet."); } if (!thisIsColdWallet && !thisIsHotWallet) { this.logger.LogTrace("(-)[COLDSTAKE_ADDRESSES_NOT_IN_ACCOUNTS]"); throw new WalletException("The hot and cold wallet addresses could not be found in the corresponding accounts."); } KeyId hotPubKeyHash = new BitcoinPubKeyAddress(hotWalletAddress, wallet.Network).Hash; KeyId coldPubKeyHash = new BitcoinPubKeyAddress(coldWalletAddress, wallet.Network).Hash; Script destination = ColdStakingScriptTemplate.Instance.GenerateScriptPubKey(hotPubKeyHash, coldPubKeyHash); // Only normal accounts should be allowed. if (!this.GetAccounts(walletName).Any(a => a.Name == walletAccount)) { this.logger.LogTrace("(-)[COLDSTAKE_ACCOUNT_NOT_FOUND]"); throw new WalletException($"Can't find wallet account '{walletAccount}'."); } var context = new TransactionBuildContext(wallet.Network) { AccountReference = new WalletAccountReference(walletName, walletAccount), TransactionFee = feeAmount, MinConfirmations = 0, Shuffle = false, WalletPassword = walletPassword, Recipients = new List <Recipient>() { new Recipient { Amount = amount, ScriptPubKey = destination } } }; // Register the cold staking builder extension with the transaction builder. context.TransactionBuilder.Extensions.Add(new ColdStakingBuilderExtension(false)); // Build the transaction. Transaction transaction = walletTransactionHandler.BuildTransaction(context); this.logger.LogTrace("(-)"); return(transaction); }
public void CanChooseInputsForCall() { const int utxoIndex = 0; uint256 utxoId = uint256.Zero; uint256 utxoIdUnused = uint256.One; string senderAddress = uint160.Zero.ToBase58Address(this.network); string contractAddress = uint160.One.ToBase58Address(this.network); var request = new BuildCallContractTransactionRequest { Amount = "0", AccountName = "account 0", ContractAddress = contractAddress, FeeAmount = "0.01", GasLimit = 100_000, GasPrice = 100, MethodName = "TestMethod", WalletName = "wallet", Password = "******", Sender = senderAddress, Outpoints = new List <OutpointRequest> { new OutpointRequest { Index = utxoIndex, TransactionId = utxoId.ToString() }, } }; this.walletManager.Setup(x => x.GetAddressBalance(request.Sender)) .Returns(new AddressBalance { Address = senderAddress, AmountConfirmed = new Money(100, MoneyUnit.BTC) }); this.walletManager.Setup(x => x.GetSpendableTransactionsInWallet(It.IsAny <string>(), 0)) .Returns(new List <UnspentOutputReference> { new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoId, Index = utxoIndex, } }, new UnspentOutputReference { Address = new HdAddress { Address = senderAddress }, Transaction = new TransactionData { Id = utxoIdUnused, Index = utxoIndex, } } }); var wallet = new Features.Wallet.Wallet(); wallet.AccountsRoot.Add(new AccountRoot(wallet)); var account0 = new HdAccount(wallet.AccountsRoot.First().Accounts) { Name = request.AccountName }; account0.ExternalAddresses.Add(new HdAddress() { Address = senderAddress }); this.walletManager.Setup(x => x.GetWallet(request.WalletName)) .Returns(wallet); var reserveUtxoService = new ReserveUtxoService(this.loggerFactory, new Mock <ISignals>().Object); var service = new SmartContractTransactionService( this.network, this.walletManager.Object, this.walletTransactionHandler.Object, this.stringSerializer.Object, this.callDataSerializer.Object, this.addressGenerator.Object, this.stateRepository.Object, reserveUtxoService); BuildCallContractTransactionResponse result = service.BuildCallTx(request); this.walletTransactionHandler.Verify(x => x.BuildTransaction(It.Is <TransactionBuildContext>(y => y.SelectedInputs.Count == 1))); }
public void GetSpendableTransactionsWithSpendableTransactionsReturnsSpendableTransactions() { var store = new WalletMemoryStore(); var account = new HdAccount(); account.ExternalAddresses.Add(new HdAddress { Index = 2, Address = "2" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(1), 1), Address = "2", Id = new uint256(15), Index = 7, SpendingDetails = new SpendingDetails { TransactionId = new uint256(1) } } }); account.ExternalAddresses.Add(new HdAddress { Index = 3, Address = "3" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(2), 1), Address = "3", Id = new uint256(18), Index = 8 } }); account.ExternalAddresses.Add(new HdAddress { Index = 1, Address = "1" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(3), 1), Address = "1", Id = new uint256(19), Index = 9, SpendingDetails = new SpendingDetails { TransactionId = new uint256(1) } } }); account.ExternalAddresses.Add(new HdAddress { Index = 6 }); account.InternalAddresses.Add(new HdAddress { Index = 4, Address = "4" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(4), 1), Address = "4", Id = new uint256(15), Index = 10, SpendingDetails = new SpendingDetails { TransactionId = new uint256(1) } } }); account.InternalAddresses.Add(new HdAddress { Index = 5, Address = "5" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(5), 1), Address = "5", Id = new uint256(18), Index = 11 } }); account.InternalAddresses.Add(new HdAddress { Index = 6 }); account.InternalAddresses.Add(new HdAddress { Index = 6, Address = "6" }); store.Add(new List <TransactionOutputData> { new TransactionOutputData { OutPoint = new OutPoint(new uint256(6), 1), Address = "6", Id = new uint256(19), Index = 12, SpendingDetails = new SpendingDetails { TransactionId = new uint256(1) } } }); IEnumerable <UnspentOutputReference> result = account.GetSpendableTransactions(store, 100, 10, 0); Assert.Equal(2, result.Count()); Assert.Equal(8, result.ElementAt(0).Transaction.Index); Assert.Equal(new uint256(18), result.ElementAt(0).Transaction.Id); Assert.Equal(11, result.ElementAt(1).Transaction.Index); Assert.Equal(new uint256(18), result.ElementAt(1).Transaction.Id); }
/// <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 IActionResult GetHistory(string walletName, string address) { if (string.IsNullOrWhiteSpace(walletName)) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No wallet name", "No wallet name provided")); } if (string.IsNullOrWhiteSpace(address)) { return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "No address", "No address provided")); } try { var transactionItems = new List <ContractTransactionItem>(); HdAccount account = this.walletManager.GetAccounts(walletName).First(); // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them. IEnumerable <AccountHistory> accountsHistory = this.walletManager.GetHistory(walletName, account.Name); // Wallet manager returns only 1 when an account name is specified. AccountHistory accountHistory = accountsHistory.First(); List <FlatHistory> items = accountHistory.History.Where(x => x.Address.Address == address).ToList(); // Represents a sublist of transactions associated with receive addresses + a sublist of already spent transactions associated with change addresses. // In effect, we filter out 'change' transactions that are not spent, as we don't want to show these in the history. List <FlatHistory> history = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && t.Transaction.IsSpent())).ToList(); // TransactionData in history is confusingly named. A "TransactionData" actually represents an input, and the outputs that spend it are "SpendingDetails". // There can be multiple "TransactionData" which have the same "SpendingDetails". // For SCs we need to group spending details by their transaction ID, to get all the inputs related to the same outputs. // Each group represents 1 SC transaction. // Each item.Transaction in a group is an input. // Each item.Transaction.SpendingDetails in the group represent the outputs, and they should all be the same so we can pick any one. var scTransactions = history .Where(item => item.Transaction.SpendingDetails != null) .Where(item => item.Transaction.SpendingDetails.Payments.Any(x => x.DestinationScriptPubKey.IsSmartContractExec())) .GroupBy(item => item.Transaction.SpendingDetails.TransactionId) .Select(g => new { TransactionId = g.Key, InputAmount = g.Sum(i => i.Transaction.Amount), // Sum the inputs to the SC transaction. Outputs = g.First().Transaction.SpendingDetails.Payments, // Each item in the group will have the same outputs. OutputAmount = g.First().Transaction.SpendingDetails.Payments.Sum(o => o.Amount), BlockHeight = g.First().Transaction.SpendingDetails.BlockHeight // Each item in the group will have the same block height. }) .ToList(); foreach (var scTransaction in scTransactions) { // Consensus rules state that each transaction can have only one smart contract exec output, so FirstOrDefault is correct. PaymentDetails scPayment = scTransaction.Outputs?.FirstOrDefault(x => x.DestinationScriptPubKey.IsSmartContractExec()); if (scPayment == null) { continue; } Receipt receipt = this.receiptRepository.Retrieve(scTransaction.TransactionId); Result <ContractTxData> txDataResult = this.callDataSerializer.Deserialize(scPayment.DestinationScriptPubKey.ToBytes()); if (txDataResult.IsFailure) { continue; } ContractTxData txData = txDataResult.Value; // If the receipt is not available yet, we don't know how much gas was consumed so use the full gas budget. ulong gasFee = receipt != null ? receipt.GasUsed * receipt.GasPrice : txData.GasCostBudget; long totalFees = scTransaction.InputAmount - scTransaction.OutputAmount; Money transactionFee = Money.FromUnit(totalFees, MoneyUnit.Satoshi) - Money.FromUnit(txData.GasCostBudget, MoneyUnit.Satoshi); var result = new ContractTransactionItem { Amount = scPayment.Amount.ToUnit(MoneyUnit.Satoshi), BlockHeight = scTransaction.BlockHeight, Hash = scTransaction.TransactionId, TransactionFee = transactionFee.ToUnit(MoneyUnit.Satoshi), GasFee = gasFee }; if (scPayment.DestinationScriptPubKey.IsSmartContractCreate()) { result.Type = ContractTransactionItemType.ContractCreate; result.To = receipt?.NewContractAddress?.ToBase58Address(this.network) ?? string.Empty; } else if (scPayment.DestinationScriptPubKey.IsSmartContractCall()) { result.Type = ContractTransactionItemType.ContractCall; result.To = txData.ContractAddress.ToBase58Address(this.network); } transactionItems.Add(result); } return(this.Json(transactionItems.OrderByDescending(x => x.BlockHeight ?? Int32.MaxValue))); } catch (Exception e) { this.logger.LogError("Exception occurred: {0}", e.ToString()); return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString())); } }
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)); } }