예제 #1
0
        /// <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);
        }
예제 #2
0
        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);
        }
예제 #4
0
        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);
        }
예제 #5
0
        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);
        }
예제 #6
0
        /// <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);
        }
예제 #9
0
        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));
            }
        }
예제 #10
0
        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);
        }
예제 #12
0
        /// <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));
        }
예제 #13
0
        /// <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);
        }
예제 #16
0
        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);
        }
예제 #18
0
        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);
        }
예제 #20
0
        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)));
        }
예제 #27
0
        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);
        }
예제 #29
0
        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));
            }
        }