示例#1
0
        /// <summary>
        /// Find all unspent transaction output of the account
        /// </summary>
        public IDictionary <Coin, bool> GetUnspentCoins(SafeAccount account = null)
        {
            AssertAccount(account);

            var unspentCoins = new Dictionary <Coin, bool>();

            var trackedScriptPubkeys = GetTrackedScriptPubKeysBySafeAccount(account);

            // 1. Go through all the transactions and their outputs
            foreach (SmartTransaction tx in Tracker
                     .TrackedTransactions
                     .Where(x => x.Height != Height.Unknown))
            {
                foreach (var coin in tx.Transaction.Outputs.AsCoins())
                {
                    // 2. Check if the coin comes with our account
                    if (trackedScriptPubkeys.Contains(coin.ScriptPubKey))
                    {
                        // 3. Check if coin is unspent, if so add to our utxoSet
                        if (IsUnspent(coin))
                        {
                            unspentCoins.Add(coin, tx.Confirmed);
                        }
                    }
                }
            }

            return(unspentCoins);
        }
示例#2
0
        public HashSet <Script> GetTrackedScriptPubKeysBySafeAccount(SafeAccount account = null)
        {
            var maxTracked = Tracker.TrackedScriptPubKeys.Count;
            var allPossiblyTrackedAddresses = new HashSet <BitcoinAddress>();

            foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Receive, account))
            {
                allPossiblyTrackedAddresses.Add(address);
            }
            foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.Change, account))
            {
                allPossiblyTrackedAddresses.Add(address);
            }
            foreach (var address in Safe.GetFirstNAddresses(maxTracked, HdPathType.NonHardened, account))
            {
                allPossiblyTrackedAddresses.Add(address);
            }

            var actuallyTrackedScriptPubKeys = new HashSet <Script>();

            foreach (var address in allPossiblyTrackedAddresses)
            {
                if (Tracker.TrackedScriptPubKeys.Any(x => x == address.ScriptPubKey))
                {
                    actuallyTrackedScriptPubKeys.Add(address.ScriptPubKey);
                }
            }

            return(actuallyTrackedScriptPubKeys);
        }
示例#3
0
        public BaseResponse BuildTransaction(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType)
        {
            if (password != _password)
            {
                throw new InvalidOperationException("Wrong password");
            }
            var result = _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, Config.CanSpendUnconfirmed).Result;

            if (result.Success)
            {
                return(new BuildTransactionResponse
                {
                    SpendsUnconfirmed = result.SpendsUnconfirmed,
                    Fee = result.Fee.ToString(false, true),
                    FeePercentOfSent = result.FeePercentOfSent.ToString("0.##"),
                    Hex = result.Transaction.ToHex(),
                    Transaction = result.Transaction.ToString()
                });
            }
            else
            {
                return(new FailureResponse
                {
                    Message = result.FailingReason
                });
            }
        }
示例#4
0
        public AvailableAmount GetBalance(out IDictionary <Coin, bool> unspentCoins, SafeAccount account = null)
        {
            // 1. Find all coins I can spend from the account
            Debug.WriteLine("Finding all unspent coins...");
            unspentCoins = GetUnspentCoins(account);

            // 2. How much money we can spend?
            var confirmedAvailableAmount   = Money.Zero;
            var unconfirmedAvailableAmount = Money.Zero;

            foreach (var elem in unspentCoins)
            {
                // Value true if confirmed
                if (elem.Value)
                {
                    confirmedAvailableAmount += elem.Key.Amount as Money;
                }
                else
                {
                    unconfirmedAvailableAmount += elem.Key.Amount as Money;
                }
            }

            return(new AvailableAmount
            {
                Confirmed = confirmedAvailableAmount,
                Unconfirmed = unconfirmedAvailableAmount
            });
        }
示例#5
0
        public async Task CreationTestsAsync()
        {
            for (int i = 0; i < 2; i++)
            {
                var network = i == 0 ? Network.Main : Network.TestNet;

                const string path          = "Wallets/TestWallet.json";
                const string password      = "******";
                const string recoveredPath = "Wallets/RecoveredTestWallet.json";

                if (File.Exists(path))
                {
                    File.Delete(path);
                }
                if (File.Exists(recoveredPath))
                {
                    File.Delete(recoveredPath);
                }

                var result = await Safe.CreateAsync(password, path, network);

                var safe       = result.Safe;
                var mnemonic   = result.Mnemonic;
                var loadedSafe = await Safe.LoadAsync(password, path, network);

                var wantedCreation = DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
                var recoverdSafe   = await Safe.RecoverAsync(mnemonic, password, recoveredPath, network, wantedCreation);

                var alice = new SafeAccount(3);
                var bob   = new SafeAccount(4);
                try
                {
                    foreach (AddressType addressType in Enum.GetValues(typeof(AddressType)))
                    {
                        Assert.Equal(safe.GetScriptPubKey(addressType, 3, account: alice), recoverdSafe.GetScriptPubKey(addressType, 3, account: alice));
                        Assert.Equal(safe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, HdPathType.Change, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetScriptPubKey(addressType, 3, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetScriptPubKey(addressType, 4, HdPathType.NonHardened, account: bob));
                        Assert.NotEqual(safe.GetScriptPubKey(addressType, 4, account: alice), safe.GetScriptPubKey(addressType, 4));
                    }

                    Assert.Equal(DateTimeOffset.UtcNow.Date, safe.CreationTime.Date);
                    Assert.InRange(safe.CreationTime, Safe.EarliestPossibleCreationTime, DateTimeOffset.UtcNow);
                    Assert.True(wantedCreation < recoverdSafe.CreationTime);
                    Assert.Equal(network, safe.Network);
                    Assert.Equal(network, loadedSafe.Network);
                    Assert.Equal(network, recoverdSafe.Network);
                }
                finally
                {
                    safe?.Delete();
                    recoverdSafe?.Delete();
                }
            }
        }
示例#6
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="account">if null then default safe, if doesn't contain, then exception</param>
        /// <returns></returns>
        public IEnumerable <SafeHistoryRecord> GetSafeHistory(SafeAccount account = null)
        {
            AssertAccount(account);

            var safeHistory = new HashSet <SafeHistoryRecord>();

            var transactions  = GetAllChainAndMemPoolTransactionsBySafeAccount(account);
            var scriptPubKeys = GetTrackedScriptPubKeysBySafeAccount(account);

            foreach (SmartTransaction transaction in transactions)
            {
                SafeHistoryRecord record = new SafeHistoryRecord();
                record.TransactionId = transaction.GetHash();
                record.BlockHeight   = transaction.Height;
                // todo: the mempool could note when it seen the transaction the first time
                record.TimeStamp = !transaction.Confirmed
                                        ? DateTimeOffset.UtcNow
                                        : HeaderChain.GetBlock(transaction.Height).Header.BlockTime;

                record.Amount = Money.Zero;                 //for now

                // how much came to our scriptpubkeys
                foreach (var output in transaction.Transaction.Outputs)
                {
                    if (scriptPubKeys.Contains(output.ScriptPubKey))
                    {
                        record.Amount += output.Value;
                    }
                }

                foreach (var input in transaction.Transaction.Inputs)
                {
                    // do we have the input?
                    SmartTransaction inputTransaction = transactions.FirstOrDefault(x => x.GetHash() == input.PrevOut.Hash);
                    if (default(SmartTransaction) != inputTransaction)
                    {
                        // if yes then deduct from amount (bitcoin output cannot be partially spent)
                        var prevOutput = inputTransaction.Transaction.Outputs[input.PrevOut.N];
                        if (scriptPubKeys.Contains(prevOutput.ScriptPubKey))
                        {
                            record.Amount -= prevOutput.Value;
                        }
                    }
                    // if no then whatever
                }

                safeHistory.Add(record);
            }

            return(safeHistory.ToList().OrderBy(x => x.TimeStamp));
        }
示例#7
0
 private void AssertAccount(SafeAccount account)
 {
     if (account == null)
     {
         if (!TracksDefaultSafe)
         {
             throw new NotSupportedException($"{nameof(TracksDefaultSafe)} cannot be {TracksDefaultSafe}");
         }
     }
     else
     {
         if (!SafeAccounts.Any(x => x.Id == account.Id))
         {
             throw new NotSupportedException($"{nameof(SafeAccounts)} does not contain the provided {nameof(account)}");
         }
     }
 }
示例#8
0
        public async Task <BaseResponse> BuildTransactionAsync(string password, SafeAccount safeAccount, BitcoinAddress address, Money amount, FeeType feeType, bool subtractFeeFromAmount, Script customChangeScriptPubKey, IEnumerable <OutPoint> allowedInputs)
        {
            if (password != _password)
            {
                throw new InvalidOperationException("Wrong password");
            }
            var result = await _walletJob.BuildTransactionAsync(address.ScriptPubKey, amount, feeType, safeAccount, (bool)Global.Config.CanSpendUnconfirmed, subtractFeeFromAmount, customChangeScriptPubKey, allowedInputs);

            if (result.Success)
            {
                var inputs = new List <TransactionInputModel>();
                foreach (Coin coin in result.SpentCoins)
                {
                    inputs.Add(new TransactionInputModel
                    {
                        Amount  = coin.Amount.ToString(false, true),
                        Address = coin.ScriptPubKey.GetDestinationAddress(Network).ToString(),
                        Hash    = coin.Outpoint.Hash.ToString(),
                        Index   = (int)coin.Outpoint.N
                    });
                }

                return(new BuildTransactionResponse
                {
                    SpendsUnconfirmed = result.SpendsUnconfirmed,
                    Fee = result.Fee.ToString(false, true),
                    FeePercentOfSent = result.FeePercentOfSent.ToString("0.##"),
                    Hex = result.Transaction.ToHex(),
                    ActiveOutputAddress = result.ActiveOutput.ScriptPubKey.GetDestinationAddress(Network).ToString(),
                    ActiveOutputAmount = result.ActiveOutput.Value.ToString(false, true),
                    ChangeOutputAddress = result.ChangeOutput?.ScriptPubKey?.GetDestinationAddress(Network)?.ToString() ?? "",
                    ChangeOutputAmount = result.ChangeOutput?.Value?.ToString(false, true) ?? "0",
                    NumberOfInputs = result.SpentCoins.Count(),
                    Inputs = inputs.ToArray(),
                    Transaction = result.Transaction.ToString()
                });
            }
            else
            {
                return(new FailureResponse
                {
                    Message = result.FailingReason
                });
            }
        }
示例#9
0
        public void CreationTests()
        {
            for (int i = 0; i < 2; i++)
            {
                var network = i == 0 ? Network.Main : Network.TestNet;

                Mnemonic     mnemonic;
                const string path     = "Wallets/TestWallet.json";
                const string password = "******";

                var safe       = Safe.Create(out mnemonic, password, path, network);
                var loadedSafe = Safe.Load(password, path);

                var wantedCreation = DateTimeOffset.ParseExact("1998-01-01", "yyyy-MM-dd", CultureInfo.InvariantCulture);
                var recoverdSafe   = Safe.Recover(mnemonic, password, "Wallets/RecoveredTestWallet.json", network, wantedCreation);

                var alice = new SafeAccount(3);
                var bob   = new SafeAccount(4);
                try
                {
                    foreach (AddressType addressType in Enum.GetValues(typeof(AddressType)))
                    {
                        Assert.Equal(safe.GetAddress(addressType, 3, account: alice), recoverdSafe.GetAddress(addressType, 3, account: alice));
                        Assert.Equal(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.Change, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetAddress(addressType, 3, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice));
                        Assert.NotEqual(safe.GetAddress(addressType, 4, HdPathType.NonHardened, account: alice), recoverdSafe.GetAddress(addressType, 4, HdPathType.NonHardened, account: bob));
                        Assert.NotEqual(safe.GetAddress(addressType, 4, account: alice), safe.GetAddress(addressType, 4));
                    }

                    Assert.Equal(DateTimeOffset.UtcNow.Date, safe.CreationTime.Date);
                    Assert.True(Safe.EarliestPossibleCreationTime < safe.CreationTime);
                    Assert.True(wantedCreation < recoverdSafe.CreationTime);
                    Assert.Equal(network, safe.Network);
                    Assert.Equal(network, loadedSafe.Network);
                    Assert.Equal(network, recoverdSafe.Network);
                }
                finally
                {
                    safe.Delete();
                    recoverdSafe.Delete();
                }
            }
        }
示例#10
0
        /// <returns>null if didn't fail</returns>
        private FailureResponse GetAccount(string account, out SafeAccount safeAccount)
        {
            safeAccount = null;
            if (account == null)
            {
                return new FailureResponse {
                           Message = "No request body specified"
                }
            }
            ;

            if (!Global.WalletWrapper.IsDecrypted)
            {
                return new FailureResponse {
                           Message = "Wallet isn't decrypted"
                }
            }
            ;

            var trimmed = account;

            if (string.Equals(trimmed, "alice", StringComparison.OrdinalIgnoreCase))
            {
                safeAccount = Global.WalletWrapper.AliceAccount;

                return(null);
            }
            else if (string.Equals(trimmed, "bob", StringComparison.OrdinalIgnoreCase))
            {
                safeAccount = Global.WalletWrapper.BobAccount;
                return(null);
            }
            else
            {
                return new FailureResponse {
                           Message = "Wrong account"
                }
            };
        }
示例#11
0
        public IEnumerable <Script> GetUnusedScriptPubKeys(SafeAccount account = null, HdPathType hdPathType = HdPathType.Receive)
        {
            AssertAccount(account);

            HashSet <Script> scriptPubKeys = new HashSet <Script>();
            int i = 0;

            while (true)
            {
                Script scriptPubkey = account == null?Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey;

                if (Tracker.IsClean(scriptPubkey))
                {
                    scriptPubKeys.Add(scriptPubkey);

                    if (scriptPubKeys.Count >= MaxCleanAddressCount)
                    {
                        return(scriptPubKeys);
                    }
                }
                i++;
            }
        }
示例#12
0
        private void UpdateSafeTrackingByPath(HdPathType hdPathType, SafeAccount account = null)
        {
            int i          = 0;
            var cleanCount = 0;

            while (true)
            {
                Script scriptPubkey = account == null?Safe.GetAddress(i, hdPathType).ScriptPubKey : Safe.GetAddress(i, hdPathType, account).ScriptPubKey;

                Tracker.TrackedScriptPubKeys.Add(scriptPubkey);

                // if clean elevate cleancount and if max reached don't look for more
                if (Tracker.IsClean(scriptPubkey))
                {
                    cleanCount++;
                    if (cleanCount > MaxCleanAddressCount)
                    {
                        return;
                    }
                }

                i++;
            }
        }
示例#13
0
 public HistoryResponse GetHistoryResponse(SafeAccount account) => account == AliceAccount ? _historyResponseAlice : _historyResponseBob;
示例#14
0
 public ReceiveResponse GetReceiveResponse(SafeAccount account) => account == AliceAccount ? _receiveResponseAlice : _receiveResponseBob;
示例#15
0
 public Money GetIncoming(SafeAccount account) => account == AliceAccount ? _incomingAlice : _incomingBob;
示例#16
0
 public Money GetAvailable(SafeAccount account) => account == AliceAccount ? _availableAlice : _availableBob;
示例#17
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="scriptPubKeyToSend"></param>
        /// <param name="amount">If Money.Zero then spend all available amount</param>
        /// <param name="feeType"></param>
        /// <param name="account"></param>
        /// <param name="allowUnconfirmed">Allow to spend unconfirmed transactions, if necessary</param>
        /// <returns></returns>
        public async Task <BuildTransactionResult> BuildTransactionAsync(Script scriptPubKeyToSend, Money amount, FeeType feeType, SafeAccount account = null, bool allowUnconfirmed = false)
        {
            try
            {
                AssertAccount(account);

                // 1. Get the script pubkey of the change.
                Debug.WriteLine("Select change address...");
                Script changeScriptPubKey = GetUnusedScriptPubKeys(account, HdPathType.Change).FirstOrDefault();

                // 2. Find all coins I can spend from the account
                // 3. How much money we can spend?
                Debug.WriteLine("Calculating available amount...");
                IDictionary <Coin, bool> unspentCoins;
                AvailableAmount          balance = GetBalance(out unspentCoins, account);
                Money spendableConfirmedAmount   = balance.Confirmed;
                Money spendableUnconfirmedAmount =
                    allowUnconfirmed ? balance.Unconfirmed : Money.Zero;
                Debug.WriteLine($"Spendable confirmed amount: {spendableConfirmedAmount}");
                Debug.WriteLine($"Spendable unconfirmed amount: {spendableUnconfirmedAmount}");

                BuildTransactionResult successfulResult = new BuildTransactionResult
                {
                    Success       = true,
                    FailingReason = ""
                };

                // 4. Get and calculate fee
                Debug.WriteLine("Calculating dynamic transaction fee...");
                Money feePerBytes = null;
                try
                {
                    feePerBytes = await QueryFeePerBytesAsync(feeType).ConfigureAwait(false);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                    return(new BuildTransactionResult
                    {
                        Success = false,
                        FailingReason = $"Couldn't calculate transaction fee. Reason:{Environment.NewLine}{ex}"
                    });
                }

                bool spendAll = amount == Money.Zero;
                int  inNum;
                if (spendAll)
                {
                    inNum = unspentCoins.Count;
                }
                else
                {
                    const int expectedMinTxSize = 1 * 148 + 2 * 34 + 10 - 1;
                    try
                    {
                        inNum = SelectCoinsToSpend(unspentCoins, amount + feePerBytes * expectedMinTxSize).Count;
                    }
                    catch (InsufficientBalanceException)
                    {
                        return(NotEnoughFundsBuildTransactionResult);
                    }
                }

                const int outNum          = 2;                                      // 1 address to send + 1 for change
                var       estimatedTxSize = inNum * 148 + outNum * 34 + 10 + inNum; // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
                Debug.WriteLine($"Estimated tx size: {estimatedTxSize} bytes");
                Money fee = feePerBytes * estimatedTxSize;
                Debug.WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                successfulResult.Fee = fee;

                // 5. How much to spend?
                Money amountToSend = null;
                if (spendAll)
                {
                    if (allowUnconfirmed)
                    {
                        amountToSend = spendableConfirmedAmount + spendableUnconfirmedAmount;
                    }
                    else
                    {
                        amountToSend = spendableConfirmedAmount;
                    }
                    amountToSend -= fee;
                }
                else
                {
                    amountToSend = amount;
                }

                // 6. Do some checks
                if (amountToSend < Money.Zero)
                {
                    return(NotEnoughFundsBuildTransactionResult);
                }
                if (allowUnconfirmed)
                {
                    if (spendableConfirmedAmount + spendableUnconfirmedAmount < amountToSend + fee)
                    {
                        return(NotEnoughFundsBuildTransactionResult);
                    }
                }
                else
                {
                    if (spendableConfirmedAmount < amountToSend + fee)
                    {
                        return(NotEnoughFundsBuildTransactionResult);
                    }
                }

                decimal feePc = (100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC);
                successfulResult.FeePercentOfSent = feePc;
                if (feePc > 1)
                {
                    Debug.WriteLine("");
                    Debug.WriteLine($"The transaction fee is {feePc:0.#}% of your transaction amount.");
                    Debug.WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                    Debug.WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                }

                var confirmedAvailableAmount = spendableConfirmedAmount - spendableUnconfirmedAmount;
                var totalOutAmount           = amountToSend + fee;
                if (confirmedAvailableAmount < totalOutAmount)
                {
                    var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount;
                    Debug.WriteLine("");
                    Debug.WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC):0.#############################} unconfirmed btc.");
                    successfulResult.SpendsUnconfirmed = true;
                }

                // 7. Select coins
                Debug.WriteLine("Selecting coins...");
                HashSet <Coin> coinsToSpend = SelectCoinsToSpend(unspentCoins, totalOutAmount);

                // 8. Get signing keys
                var signingKeys = new HashSet <ISecret>();
                foreach (var coin in coinsToSpend)
                {
                    var signingKey = Safe.FindPrivateKey(coin.ScriptPubKey.GetDestinationAddress(Safe.Network), Tracker.TrackedScriptPubKeys.Count, account);
                    signingKeys.Add(signingKey);
                }

                // 9. Build the transaction
                Debug.WriteLine("Signing transaction...");
                var builder = new TransactionBuilder();
                var tx      = builder
                              .AddCoins(coinsToSpend)
                              .AddKeys(signingKeys.ToArray())
                              .Send(scriptPubKeyToSend, amountToSend)
                              .SetChange(changeScriptPubKey)
                              .SendFees(fee)
                              .BuildTransaction(true);

                if (!builder.Verify(tx))
                {
                    return new BuildTransactionResult
                           {
                               Success       = false,
                               FailingReason = "Couldn't build the transaction"
                           }
                }
                ;

                successfulResult.Transaction = tx;
                return(successfulResult);
            }
            catch (Exception ex)
            {
                return(new BuildTransactionResult
                {
                    Success = false,
                    FailingReason = ex.ToString()
                });
            }
        }
示例#18
0
        public void MaxAmountTest()
        {
            Network      network  = Network.TestNet;
            SafeAccount  account  = new SafeAccount(1);
            string       path     = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json");
            const string password = "";
            Safe         safe     = Safe.Load(password, path);

            Assert.Equal(network, safe.Network);
            Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}");

            // create walletjob
            WalletJob walletJob = new WalletJob(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe: false, accountsToTrack: account);

            // note some event
            WalletJob.ConnectedNodeCountChanged += delegate
            {
                if (WalletJob.MaxConnectedNodeCount == WalletJob.ConnectedNodeCount)
                {
                    Debug.WriteLine(
                        $"{nameof(WalletJob.MaxConnectedNodeCount)} reached: {WalletJob.MaxConnectedNodeCount}");
                }
                else
                {
                    Debug.WriteLine($"{nameof(WalletJob.ConnectedNodeCount)}: {WalletJob.ConnectedNodeCount}");
                }
            };
            walletJob.StateChanged += delegate
            {
                Debug.WriteLine($"{nameof(walletJob.State)}: {walletJob.State}");
            };

            // start syncing
            var  cts           = new CancellationTokenSource();
            var  walletJobTask = walletJob.StartAsync(cts.Token);
            Task reportTask    = Helpers.ReportAsync(cts.Token, walletJob);

            try
            {
                // wait until blocks are synced
                while (walletJob.State <= WalletState.SyncingMemPool)
                {
                    Task.Delay(1000).Wait();
                }

                var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();

                IDictionary <Coin, bool> unspentCoins;
                var bal = walletJob.GetBalance(out unspentCoins, account);

                var res = walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account,
                                                          allowUnconfirmed: true).Result;

                Assert.True(res.Success);
                Assert.True(res.FailingReason == "");
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {res.Transaction}");

                var foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.True(bal.Confirmed + bal.Unconfirmed - res.Fee == output.Value);
                    }
                }
                Assert.True(foundReceive);

                var txProbArrived = false;
                var prevCount     = walletJob.Tracker.TrackedTransactions.Count;
                walletJob.Tracker.TrackedTransactions.CollectionChanged += delegate
                {
                    var actCount = walletJob.Tracker.TrackedTransactions.Count;
                    // if arrived
                    if (actCount > prevCount)
                    {
                        txProbArrived = true;
                    }
                    else
                    {
                        prevCount = actCount;
                    }
                };

                var sendRes = walletJob.SendTransactionAsync(res.Transaction).Result;
                Assert.True(sendRes.Success);
                Assert.True(sendRes.FailingReason == "");

                while (txProbArrived == false)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    Task.Delay(1000).Wait();
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.True(walletJob.Tracker.TrackedTransactions.Any(x => x.Transaction.GetHash() == res.Transaction.GetHash()));
                Debug.WriteLine("Transaction arrived");
            }
            finally
            {
                cts.Cancel();
                Task.WhenAll(reportTask, walletJobTask).Wait();
            }
        }
示例#19
0
        public async Task SendsFailGracefullyTestAsync()
        {
            Network      network  = Network.TestNet;
            SafeAccount  account  = new SafeAccount(1);
            string       path     = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json");
            const string password = "";
            Safe         safe     = await Safe.LoadAsync(password, path, network);

            Assert.Equal(network, safe.Network);
            Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}");

            // create walletjob
            WalletJob walletJob = new WalletJob();
            await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account);

            // note some event
            walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged;
            walletJob.StateChanged += WalletJob_StateChanged;

            // start syncing
            var  cts           = new CancellationTokenSource();
            var  walletJobTask = walletJob.StartAsync(cts.Token);
            Task reportTask    = Helpers.ReportAsync(cts.Token, walletJob);

            try
            {
                // wait until blocks are synced
                while (walletJob.State <= WalletState.SyncingMemPool)
                {
                    await Task.Delay(1000);
                }

                var history = await walletJob.GetSafeHistoryAsync(account);

                foreach (var record in history)
                {
                    Debug.WriteLine($"{record.TransactionId} {record.Amount} {record.Confirmed}");
                }

                var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                var bal = (await walletJob.GetBalanceAsync(account)).Available;

                // Not enough fee
                Money amountToSend = (bal.Confirmed + bal.Unconfirmed) - new Money(1m, MoneyUnit.Satoshi);
                var   res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                           allowUnconfirmed : true);

                Assert.False(res.Success);
                Assert.NotEmpty(res.FailingReason);
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // That's not how you spend all
                amountToSend = (bal.Confirmed + bal.Unconfirmed);
                res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed : true);

                Assert.False(res.Success);
                Assert.NotEmpty(res.FailingReason);
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Too much
                amountToSend = (bal.Confirmed + bal.Unconfirmed) + new Money(1, MoneyUnit.BTC);
                res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed : true);

                Assert.True(res.Success == false);
                Assert.True(res.FailingReason != "");
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Minus
                amountToSend = new Money(-1m, MoneyUnit.BTC);
                res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed : true);

                Assert.False(res.Success);
                Assert.NotEmpty(res.FailingReason);
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Default account is disabled
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                await Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low,
                                                                                                                  allowUnconfirmed: true)).ContinueWith(t => {});

                // No such account
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                await Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, new SafeAccount(23421),
                                                                                                                  allowUnconfirmed: true)).ContinueWith(t => { });
            }
            finally
            {
                cts?.Cancel();
                await Task.WhenAll(reportTask, walletJobTask);

                walletJob.ConnectedNodeCountChanged -= WalletJob_ConnectedNodeCountChanged;
                walletJob.StateChanged -= WalletJob_StateChanged;

                cts?.Dispose();
                reportTask?.Dispose();
                walletJobTask?.Dispose();
            }
        }
示例#20
0
        public async Task SendTestAsync()
        {
            Network      network  = Network.TestNet;
            SafeAccount  account  = new SafeAccount(1);
            string       path     = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json");
            const string password = "";
            Safe         safe     = await Safe.LoadAsync(password, path);

            Assert.Equal(network, safe.Network);
            Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}");

            // create walletjob
            WalletJob walletJob = new WalletJob();
            await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account);

            // note some event
            walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged;
            walletJob.StateChanged += WalletJob_StateChanged;

            // start syncing
            var  cts           = new CancellationTokenSource();
            var  walletJobTask = walletJob.StartAsync(cts.Token);
            Task reportTask    = Helpers.ReportAsync(cts.Token, walletJob);

            try
            {
                // wait until blocks are synced
                while (walletJob.State <= WalletState.SyncingMemPool)
                {
                    await Task.Delay(1000);
                }

                foreach (var r in await walletJob.GetSafeHistoryAsync(account))
                {
                    Debug.WriteLine(r.TransactionId);
                }

                # region Basic

                var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                var getBalanceResult = await walletJob.GetBalanceAsync(account);

                var   bal          = getBalanceResult.Available;
                Money amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                var   res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                           allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                var foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                _txProbArrived    = false;
                _prevCount        = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count;
                _currentWalletJob = walletJob;
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync;

                var sendRes = await walletJob.SendTransactionAsync(res.Transaction);

                Assert.True(sendRes.Success);
                Assert.Empty(sendRes.FailingReason);

                while (!_txProbArrived)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    await Task.Delay(1000);
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash());
                Debug.WriteLine("Transaction arrived");


                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                bal          = (await walletJob.GetBalanceAsync(account)).Available;
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;

                #endregion

                #region SubtractFeeFromAmount

                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                getBalanceResult = await walletJob.GetBalanceAsync(account);

                bal          = getBalanceResult.Available;
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed : true,
                                                                     subtractFeeFromAmount : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend - res.Fee, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend - res.Fee, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region CustomChange

                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();
                var customChange = new Key().ScriptPubKey;

                getBalanceResult = await walletJob.GetBalanceAsync(account);

                bal          = getBalanceResult.Available;
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed : true,
                                                                     subtractFeeFromAmount : true,
                                                                     customChangeScriptPubKey : customChange);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend - res.Fee, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Equal(customChange, res.ChangeOutput.ScriptPubKey);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend - res.Fee, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region LowFee

                res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                            allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region MediumFee

                res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Medium, account,
                                                            allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region HighFee

                res = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.High, account,
                                                            allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Equal(amountToSend, res.ActiveOutput.Value);
                Assert.NotNull(res.ChangeOutput);
                Assert.Contains(res.Transaction.Outputs, x => x.Value == res.ChangeOutput.Value);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"Change Output: {res.ChangeOutput.Value.ToString(false, true)} {res.ChangeOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                Assert.InRange(res.Fee, Money.Zero, res.Fee);
                Assert.InRange(res.Fee, res.Fee, res.Fee);

                _txProbArrived    = false;
                _prevCount        = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count;
                _currentWalletJob = walletJob;
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync;

                sendRes = await walletJob.SendTransactionAsync(res.Transaction);

                Assert.True(sendRes.Success);
                Assert.Empty(sendRes.FailingReason);

                while (_txProbArrived == false)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    await Task.Delay(1000);
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash());
                Debug.WriteLine("Transaction arrived");

                #endregion

                #region MaxAmount

                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                bal = (await walletJob.GetBalanceAsync(account)).Available;

                res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account,
                                                            allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Null(res.ChangeOutput);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                Assert.Single(res.Transaction.Outputs);
                var maxBuiltTxOutput = res.Transaction.Outputs.Single();
                Assert.Equal(receive, maxBuiltTxOutput.ScriptPubKey);
                Assert.Equal(bal.Confirmed + bal.Unconfirmed - res.Fee, maxBuiltTxOutput.Value);

                #endregion

                #region InputSelection

                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                bal = (await walletJob.GetBalanceAsync(account)).Available;

                var inputCountBefore = res.SpentCoins.Count();
                res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account,
                                                            allowUnconfirmed : true,
                                                            allowedInputs : res.SpentCoins.Where((x, i) => i == 0 || i % 2 == 0).Select(x => x.Outpoint));

                Assert.True(inputCountBefore >= res.SpentCoins.Count());
                Assert.Equal(res.SpentCoins.Count(), res.Transaction.Inputs.Count);
                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Assert.Equal(receive, res.ActiveOutput.ScriptPubKey);
                Assert.Null(res.ChangeOutput);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Active Output: {res.ActiveOutput.Value.ToString(false, true)} {res.ActiveOutput.ScriptPubKey.GetDestinationAddress(network)}");
                Debug.WriteLine($"TxId: {res.Transaction.GetHash()}");

                Assert.Single(res.Transaction.Outputs);

                res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account,
                                                            allowUnconfirmed : true,
                                                            allowedInputs : new[] { res.SpentCoins.Select(x => x.Outpoint).First() });

                Assert.Single(res.Transaction.Inputs);
                Assert.Single(res.Transaction.Outputs);
                Assert.Single(res.SpentCoins);
                Assert.Null(res.ChangeOutput);

                #endregion
            }
示例#21
0
        public HashSet <SmartTransaction> GetAllChainAndMemPoolTransactionsBySafeAccount(SafeAccount account = null)
        {
            HashSet <Script> trackedScriptPubkeys = GetTrackedScriptPubKeysBySafeAccount(account);
            var foundTransactions = new HashSet <SmartTransaction>();

            foreach (var spk in trackedScriptPubkeys)
            {
                HashSet <SmartTransaction> rec;
                HashSet <SmartTransaction> spent;

                if (TryFindAllChainAndMemPoolTransactions(spk, out rec, out spent))
                {
                    foreach (var tx in rec)
                    {
                        foundTransactions.Add(tx);
                    }
                    foreach (var tx in spent)
                    {
                        foundTransactions.Add(tx);
                    }
                }
            }

            return(foundTransactions);
        }
示例#22
0
        public void SendsFailGracefullyTest()
        {
            Network      network  = Network.TestNet;
            SafeAccount  account  = new SafeAccount(1);
            string       path     = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json");
            const string password = "";
            Safe         safe     = Safe.Load(password, path);

            Assert.Equal(network, safe.Network);
            Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}");

            // create walletjob
            WalletJob walletJob = new WalletJob(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe: false, accountsToTrack: account);

            // note some event
            WalletJob.ConnectedNodeCountChanged += delegate
            {
                if (WalletJob.MaxConnectedNodeCount == WalletJob.ConnectedNodeCount)
                {
                    Debug.WriteLine(
                        $"{nameof(WalletJob.MaxConnectedNodeCount)} reached: {WalletJob.MaxConnectedNodeCount}");
                }
                else
                {
                    Debug.WriteLine($"{nameof(WalletJob.ConnectedNodeCount)}: {WalletJob.ConnectedNodeCount}");
                }
            };
            walletJob.StateChanged += delegate
            {
                Debug.WriteLine($"{nameof(walletJob.State)}: {walletJob.State}");
            };

            // start syncing
            var  cts           = new CancellationTokenSource();
            var  walletJobTask = walletJob.StartAsync(cts.Token);
            Task reportTask    = Helpers.ReportAsync(cts.Token, walletJob);

            try
            {
                // wait until blocks are synced
                while (walletJob.State <= WalletState.SyncingMemPool)
                {
                    Task.Delay(1000).Wait();
                }

                var history = walletJob.GetSafeHistory(account);
                foreach (var record in history)
                {
                    Debug.WriteLine($"{record.TransactionId} {record.Amount} {record.Confirmed}");
                }

                var receive = walletJob.GetUnusedScriptPubKeys(account, HdPathType.Receive).FirstOrDefault();

                IDictionary <Coin, bool> unspentCoins;
                var bal = walletJob.GetBalance(out unspentCoins, account);

                // Not enough fee
                Money amountToSend = (bal.Confirmed + bal.Unconfirmed) - new Money(1m, MoneyUnit.Satoshi);
                var   res          = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                     allowUnconfirmed: true).Result;
                Assert.True(res.Success == false);
                Assert.True(res.FailingReason != "");
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // That's not how you spend all
                amountToSend = (bal.Confirmed + bal.Unconfirmed);
                res          = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                               allowUnconfirmed: true).Result;
                Assert.True(res.Success == false);
                Assert.True(res.FailingReason != "");
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Too much
                amountToSend = (bal.Confirmed + bal.Unconfirmed) + new Money(1, MoneyUnit.BTC);
                res          = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                               allowUnconfirmed: true).Result;
                Assert.True(res.Success == false);
                Assert.True(res.FailingReason != "");
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Minus
                amountToSend = new Money(-1m, MoneyUnit.BTC);
                res          = walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                               allowUnconfirmed: true).Result;
                Assert.True(res.Success == false);
                Assert.True(res.FailingReason != "");
                Debug.WriteLine($"Expected FailingReason: {res.FailingReason}");

                // Default account is disabled
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low,
                                                                                                            allowUnconfirmed: true).ConfigureAwait(false)).ContinueWith(t => {}).Wait();

                // No such account
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                Assert.ThrowsAsync <NotSupportedException>(async() => await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, new SafeAccount(23421),
                                                                                                            allowUnconfirmed: true).ConfigureAwait(false)).ContinueWith(t => { }).Wait();
            }
            finally
            {
                cts.Cancel();
                Task.WhenAll(reportTask, walletJobTask).Wait();
            }
        }
示例#23
0
        public async Task SendTestAsync()
        {
            Network      network  = Network.TestNet;
            SafeAccount  account  = new SafeAccount(1);
            string       path     = Path.Combine(Helpers.CommittedWalletsFolderPath, $"Sending{network}.json");
            const string password = "";
            Safe         safe     = await Safe.LoadAsync(password, path);

            Assert.Equal(network, safe.Network);
            Debug.WriteLine($"Unique Safe ID: {safe.UniqueId}");

            // create walletjob
            WalletJob walletJob = new WalletJob();
            await walletJob.InitializeAsync(Helpers.SocksPortHandler, Helpers.ControlPortClient, safe, trackDefaultSafe : false, accountsToTrack : account);

            // note some event
            walletJob.ConnectedNodeCountChanged += WalletJob_ConnectedNodeCountChanged;
            walletJob.StateChanged += WalletJob_StateChanged;

            // start syncing
            var  cts           = new CancellationTokenSource();
            var  walletJobTask = walletJob.StartAsync(cts.Token);
            Task reportTask    = Helpers.ReportAsync(cts.Token, walletJob);

            try
            {
                // wait until blocks are synced
                while (walletJob.State <= WalletState.SyncingMemPool)
                {
                    await Task.Delay(1000);
                }

                foreach (var r in await walletJob.GetSafeHistoryAsync(account))
                {
                    Debug.WriteLine(r.TransactionId);
                }

                var record = (await walletJob.GetSafeHistoryAsync(account)).FirstOrDefault();
                Debug.WriteLine(record.Confirmed);
                Debug.WriteLine(record.Amount);

                var receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                var getBalanceResult = await walletJob.GetBalanceAsync(account);

                var   bal          = getBalanceResult.Available;
                Money amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;
                var   res          = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                           allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {res.Transaction}");

                var foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                _txProbArrived    = false;
                _prevCount        = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count;
                _currentWalletJob = walletJob;
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync;

                var sendRes = await walletJob.SendTransactionAsync(res.Transaction);

                Assert.True(sendRes.Success);
                Assert.Empty(sendRes.FailingReason);

                while (!_txProbArrived)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    await Task.Delay(1000);
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash());
                Debug.WriteLine("Transaction arrived");


                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                bal          = (await walletJob.GetBalanceAsync(account)).Available;
                amountToSend = (bal.Confirmed + bal.Unconfirmed) / 2;

                #region LowFee

                var resLow = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Low, account,
                                                                   allowUnconfirmed : true);

                Assert.True(resLow.Success);
                Assert.Empty(resLow.FailingReason);
                Debug.WriteLine($"Fee: {resLow.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {resLow.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {resLow.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {resLow.Transaction}");

                foundReceive = false;
                Assert.InRange(resLow.Transaction.Outputs.Count, 1, 2);
                foreach (var output in resLow.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region MediumFee

                var resMedium = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.Medium, account,
                                                                      allowUnconfirmed : true);

                Assert.True(resMedium.Success);
                Assert.Empty(resMedium.FailingReason);
                Debug.WriteLine($"Fee: {resMedium.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {resMedium.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {resMedium.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {resMedium.Transaction}");

                foundReceive = false;
                Assert.InRange(resMedium.Transaction.Outputs.Count, 1, 2);
                foreach (var output in resMedium.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                #region HighFee

                var resHigh = await walletJob.BuildTransactionAsync(receive, amountToSend, FeeType.High, account,
                                                                    allowUnconfirmed : true);

                Assert.True(resHigh.Success);
                Assert.Empty(resHigh.FailingReason);
                Debug.WriteLine($"Fee: {resHigh.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {resHigh.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {resHigh.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {resHigh.Transaction}");

                foundReceive = false;
                Assert.InRange(resHigh.Transaction.Outputs.Count, 1, 2);
                foreach (var output in resHigh.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(amountToSend, output.Value);
                    }
                }
                Assert.True(foundReceive);

                #endregion

                Assert.InRange(resLow.Fee, Money.Zero, resMedium.Fee);
                Assert.InRange(resMedium.Fee, resLow.Fee, resHigh.Fee);

                _txProbArrived    = false;
                _prevCount        = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count;
                _currentWalletJob = walletJob;
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync;

                sendRes = await walletJob.SendTransactionAsync(resHigh.Transaction);

                Assert.True(sendRes.Success);
                Assert.Empty(sendRes.FailingReason);

                while (_txProbArrived == false)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    await Task.Delay(1000);
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == resHigh.Transaction.GetHash());
                Debug.WriteLine("Transaction arrived");



                receive = (await walletJob.GetUnusedScriptPubKeysAsync(AddressType.Pay2WitnessPublicKeyHash, account, HdPathType.Receive)).FirstOrDefault();

                bal = (await walletJob.GetBalanceAsync(account)).Available;

                res = await walletJob.BuildTransactionAsync(receive, Money.Zero, FeeType.Low, account,
                                                            allowUnconfirmed : true);

                Assert.True(res.Success);
                Assert.Empty(res.FailingReason);
                Debug.WriteLine($"Fee: {res.Fee}");
                Debug.WriteLine($"FeePercentOfSent: {res.FeePercentOfSent} %");
                Debug.WriteLine($"SpendsUnconfirmed: {res.SpendsUnconfirmed}");
                Debug.WriteLine($"Transaction: {res.Transaction}");

                foundReceive = false;
                Assert.InRange(res.Transaction.Outputs.Count, 1, 2);
                foreach (var output in res.Transaction.Outputs)
                {
                    if (output.ScriptPubKey == receive)
                    {
                        foundReceive = true;
                        Assert.Equal(bal.Confirmed + bal.Unconfirmed - res.Fee, output.Value);
                    }
                }
                Assert.True(foundReceive);

                _txProbArrived    = false;
                _prevCount        = (await walletJob.GetTrackerAsync()).TrackedTransactions.Count;
                _currentWalletJob = walletJob;
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged += TrackedTransactions_CollectionChangedAsync;

                sendRes = await walletJob.SendTransactionAsync(res.Transaction);

                Assert.True(sendRes.Success);
                Assert.Empty(sendRes.FailingReason);

                while (_txProbArrived == false)
                {
                    Debug.WriteLine("Waiting for transaction...");
                    await Task.Delay(1000);
                }

                Debug.WriteLine("TrackedTransactions collection changed");
                Assert.Contains((await walletJob.GetTrackerAsync()).TrackedTransactions, x => x.Transaction.GetHash() == res.Transaction.GetHash());
                Debug.WriteLine("Transaction arrived");
            }
            finally
            {
                (await walletJob.GetTrackerAsync()).TrackedTransactions.CollectionChanged -= TrackedTransactions_CollectionChangedAsync;

                cts.Cancel();
                await Task.WhenAll(reportTask, walletJobTask);

                walletJob.ConnectedNodeCountChanged -= WalletJob_ConnectedNodeCountChanged;
                walletJob.StateChanged -= WalletJob_StateChanged;

                cts?.Dispose();
                reportTask?.Dispose();
                walletJobTask?.Dispose();
            }
        }