Exemplo n.º 1
0
        public const uint ImportedAccountNumber = 2147483647; // 2**31 - 1

        public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, List <AccountProperties> bip0032Accounts,
                      AccountProperties importedAccount, BlockIdentity chainTip)
        {
            if (activeChain == null)
            {
                throw new ArgumentNullException(nameof(activeChain));
            }
            if (bip0032Accounts == null)
            {
                throw new ArgumentNullException(nameof(bip0032Accounts));
            }
            if (importedAccount == null)
            {
                throw new ArgumentNullException(nameof(importedAccount));
            }
            if (chainTip == null)
            {
                throw new ArgumentNullException(nameof(chainTip));
            }

            _transactionCount = txSet.MinedTransactions.Aggregate(0, (acc, b) => acc + b.Transactions.Count) +
                                txSet.UnminedTransactions.Count;
            _bip0032Accounts = bip0032Accounts;
            _importedAccount = importedAccount;

            var totalBalance = EnumerateAccounts().Aggregate((Amount)0, (acc, a) => acc + a.Item2.TotalBalance);

            ActiveChain        = activeChain;
            RecentTransactions = txSet;
            TotalBalance       = totalBalance;
            ChainTip           = chainTip;
        }
Exemplo n.º 2
0
        private Address(BlockChainIdentity intendedBlockChain)
        {
            if (intendedBlockChain == null)
                throw new ArgumentNullException(nameof(intendedBlockChain));

            IntendedBlockChain = intendedBlockChain;
        }
Exemplo n.º 3
0
        public static string RpcListenAddress(string hostnameOrIp, BlockChainIdentity intendedNetwork)
        {
            if (intendedNetwork == null)
            {
                throw new ArgumentNullException(nameof(intendedNetwork));
            }

            // Note: The standard ports for dcrwallet gRPC are 9111, 19111, and 19558.
            // The ports used by Paymetheus are 2 greater than this to avoid conflicts with
            // other running dcrwallet instances using the default settings.
            // The +1 port is reserved for running dcrd on a nonstandard port as well.
            string port;

            if (intendedNetwork == BlockChainIdentity.MainNet)
            {
                port = "9113";
            }
            else if (intendedNetwork == BlockChainIdentity.TestNet)
            {
                port = "19113";
            }
            else if (intendedNetwork == BlockChainIdentity.SimNet)
            {
                port = "19560";
            }
            else
            {
                throw new UnknownBlockChainException(intendedNetwork);
            }

            return($"{hostnameOrIp}:{port}");
        }
Exemplo n.º 4
0
        public static string RpcListenAddress(string hostnameOrIp, BlockChainIdentity intendedNetwork)
        {
            if (intendedNetwork == null)
            {
                throw new ArgumentNullException(nameof(intendedNetwork));
            }

            string port;

            if (intendedNetwork == BlockChainIdentity.MainNet)
            {
                port = "9110";
            }
            else if (intendedNetwork == BlockChainIdentity.TestNet)
            {
                port = "19110";
            }
            else if (intendedNetwork == BlockChainIdentity.SimNet)
            {
                port = "19557";
            }
            else
            {
                throw new UnknownBlockChainException(intendedNetwork);
            }

            return($"{hostnameOrIp}:{port}");
        }
Exemplo n.º 5
0
        public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary <Account, AccountProperties> accounts, BlockIdentity chainTip)
        {
            if (activeChain == null)
            {
                throw new ArgumentNullException(nameof(activeChain));
            }
            if (accounts == null)
            {
                throw new ArgumentNullException(nameof(accounts));
            }
            if (chainTip == null)
            {
                throw new ArgumentNullException(nameof(chainTip));
            }

            var totalBalance = accounts.Aggregate((Amount)0, (acc, kvp) => acc + kvp.Value.TotalBalance);

            _transactionCount = txSet.MinedTransactions.Aggregate(0, (acc, b) => acc + b.Transactions.Count) +
                                txSet.UnminedTransactions.Count;
            _accounts = accounts;

            ActiveChain        = activeChain;
            RecentTransactions = txSet;
            TotalBalance       = totalBalance;
            ChainTip           = chainTip;
        }
Exemplo n.º 6
0
        public static async Task <SynchronizerViewModel> Startup(BlockChainIdentity activeNetwork, string walletAppDataDir)
        {
            // Begin the asynchronous reading of the certificate before starting the wallet
            // process.  This uses filesystem events to know when to begin reading the certificate,
            // and if there is too much delay between wallet writing the cert and this process
            // beginning to observe the change, the event may never fire and the cert won't be read.
            var rootCertificateTask = TransportSecurity.ReadModifiedCertificateAsync(walletAppDataDir);

            var walletProcess = WalletProcess.Start(activeNetwork, walletAppDataDir);

            WalletClient walletClient;

            try
            {
                var listenAddress   = WalletProcess.RpcListenAddress("localhost", activeNetwork);
                var rootCertificate = await rootCertificateTask;
                walletClient = await WalletClient.ConnectAsync(listenAddress, rootCertificate);
            }
            catch (Exception)
            {
                if (walletProcess.HasExited)
                {
                    throw new Exception("Wallet process closed unexpectedly");
                }
                walletProcess.KillIfExecuting();
                throw;
            }

            return(new SynchronizerViewModel(walletProcess, walletClient));
        }
Exemplo n.º 7
0
 public PayToSecSchnorrPubKeyHash(BlockChainIdentity intendedBlockChain, byte[] pubKeyHash)
     : base(intendedBlockChain)
 {
     if (pubKeyHash == null)
         throw new ArgumentNullException(nameof(pubKeyHash));
     if (pubKeyHash.Length != Ripemd160Hash.Length)
         throw new AddressException($"{nameof(pubKeyHash)} must have RIPEMD160 hash length ({Ripemd160Hash.Length})");
 }
Exemplo n.º 8
0
        private Address(BlockChainIdentity intendedBlockChain)
        {
            if (intendedBlockChain == null)
            {
                throw new ArgumentNullException(nameof(intendedBlockChain));
            }

            IntendedBlockChain = intendedBlockChain;
        }
Exemplo n.º 9
0
        public static AddressPrefix PayToSchnorrPubKeyHashPrefix(BlockChainIdentity identity)
        {
            if (identity == null)
                throw new ArgumentNullException(nameof(identity));

            if (identity == BlockChainIdentity.MainNet) return MainNetSchnorrPubKeyHashPrefix;
            else if (identity == BlockChainIdentity.TestNet) return TestNetSchnorrPubKeyHashPrefix;
            else if (identity == BlockChainIdentity.SimNet) return SimNetSchnorrPubKeyHashPrefix;
            else throw new UnknownBlockChainException(identity);
        }
Exemplo n.º 10
0
        public static AddressPrefix PayToScriptHashPrefix(BlockChainIdentity identity)
        {
            if (identity == null)
                throw new ArgumentNullException(nameof(identity));

            if (identity == BlockChainIdentity.MainNet) return MainNetPayToScriptHashPrefix;
            else if (identity == BlockChainIdentity.TestNet3) return TestNet3PayToScriptHashPrefix;
            else if (identity == BlockChainIdentity.SimNet) return SimNetPayToScriptHashPrefix;
            else throw new UnknownBlockChainException($"Unknown blockchain `{identity.Name}`");
        }
Exemplo n.º 11
0
 public PayToEd25519PubKeyHash(BlockChainIdentity intendedBlockChain, byte[] pubKeyHash)
     : base(intendedBlockChain)
 {
     if (pubKeyHash == null)
     {
         throw new ArgumentNullException(nameof(pubKeyHash));
     }
     if (pubKeyHash.Length != Ripemd160Hash.Length)
     {
         throw new AddressException($"{nameof(pubKeyHash)} must have RIPEMD160 hash length ({Ripemd160Hash.Length})");
     }
 }
Exemplo n.º 12
0
        public static async Task <SynchronizerViewModel> Startup(BlockChainIdentity activeNetwork, string walletAppDataDir,
                                                                 bool searchPath, string extraArgs)
        {
            if (activeNetwork == null)
            {
                throw new ArgumentNullException(nameof(activeNetwork));
            }
            if (walletAppDataDir == null)
            {
                throw new ArgumentNullException(nameof(walletAppDataDir));
            }
            if (extraArgs == null)
            {
                throw new ArgumentNullException(nameof(extraArgs));
            }

            // Begin the asynchronous reading of the certificate before starting the wallet
            // process.  This uses filesystem events to know when to begin reading the certificate,
            // and if there is too much delay between wallet writing the cert and this process
            // beginning to observe the change, the event may never fire and the cert won't be read.
            var rootCertificateTask = TransportSecurity.ReadModifiedCertificateAsync(walletAppDataDir);

            string walletProcessPath = null;

            if (!searchPath)
            {
                walletProcessPath = Portability.ExecutableInstallationPath(
                    Environment.OSVersion.Platform, AssemblyResources.Organization, WalletProcess.ProcessName);
            }
            KillLeftoverWalletProcess(activeNetwork);
            var walletProcess = WalletProcess.Start(activeNetwork, walletAppDataDir, walletProcessPath, extraArgs);

            WalletClient walletClient;

            try
            {
                var listenAddress   = WalletProcess.RpcListenAddress("localhost", activeNetwork);
                var rootCertificate = await rootCertificateTask;
                walletClient = await WalletClient.ConnectAsync(listenAddress, rootCertificate);
            }
            catch (Exception)
            {
                if (walletProcess.HasExited)
                {
                    throw new Exception("Wallet process closed unexpectedly");
                }
                walletProcess.KillIfExecuting();
                throw;
            }

            return(new SynchronizerViewModel(walletProcess, walletClient));
        }
Exemplo n.º 13
0
            public PayToPubKey(BlockChainIdentity intendedBlockChain, byte[] pubKey, SignatureAlgorithm.Kinds dsa)
                : base(intendedBlockChain)
            {
                if (pubKey == null)
                {
                    throw new ArgumentNullException(nameof(pubKey));
                }
                // TODO: Validate pubkey length, this could change based on the DSA
                // These validation funcs should go in Signature class.

                PubKey = pubKey;
                Dsa    = dsa;
            }
Exemplo n.º 14
0
        private static void KillLeftoverWalletProcess(BlockChainIdentity intendedNetwork)
        {
            var v4ListenAddress = WalletProcess.RpcListenAddress("127.0.0.1", intendedNetwork);
            var walletProcesses = new ManagementObjectSearcher($"SELECT * FROM Win32_Process WHERE Name='{WalletProcess.ProcessName}.exe'").Get();

            foreach (var walletProcessInfo in walletProcesses)
            {
                var commandLine = (string)walletProcessInfo["CommandLine"];
                if (commandLine.Contains($" --experimentalrpclisten={v4ListenAddress}"))
                {
                    var process = Process.GetProcessById((int)(uint)walletProcessInfo["ProcessID"]);
                    process.KillIfExecuting();
                    break;
                }
            }
        }
Exemplo n.º 15
0
        public static void ValidPayToScriptHashAddresses(string encodedAddress, BlockChainIdentity intendedBlockChain)
        {
            Address address;

            Assert.True(Address.TryDecode(encodedAddress, out address));
            address = Address.Decode(encodedAddress);

            Assert.IsType <Address.PayToScriptHash>(address);
            var p2shAddress = (Address.PayToScriptHash)address;

            Assert.Same(address.IntendedBlockChain, intendedBlockChain);

            var newAddress       = new Address.PayToSecp256k1PubKeyHash(intendedBlockChain, p2shAddress.ScriptHash);
            var reencodedAddress = address.Encode();

            Assert.Equal(encodedAddress, reencodedAddress);
        }
Exemplo n.º 16
0
        public static Process Start(BlockChainIdentity intendedNetwork, string appDataDirectory, string executablePath = null, string extraArgs = null)
        {
            if (intendedNetwork == null)
            {
                throw new ArgumentNullException(nameof(intendedNetwork));
            }
            if (appDataDirectory == null)
            {
                throw new ArgumentNullException(nameof(appDataDirectory));
            }

            var networkFlag = "";

            if (intendedNetwork != BlockChainIdentity.MainNet)
            {
                networkFlag = $"--{intendedNetwork.Name}";
            }

            var v4ListenAddress = RpcListenAddress("127.0.0.1", intendedNetwork);

            extraArgs = extraArgs ?? "";

            var processInfo = new ProcessStartInfo();

            processInfo.FileName               = executablePath ?? ProcessName;
            processInfo.Arguments              = $"{networkFlag} --noinitialload --grpclisten={v4ListenAddress} --tlscurve=P-256 --onetimetlskey --appdata=\"{appDataDirectory}\" {extraArgs}";
            processInfo.UseShellExecute        = false;
            processInfo.RedirectStandardError  = true;
            processInfo.RedirectStandardOutput = true;
            processInfo.CreateNoWindow         = true;

            try
            {
                var process = Process.Start(processInfo);
                process.ErrorDataReceived  += (sender, args) => Console.WriteLine("err> {0}", args.Data);
                process.OutputDataReceived += (sender, args) => Console.WriteLine("{0}", args.Data);
                process.BeginErrorReadLine();
                process.BeginOutputReadLine();
                return(process);
            }
            catch (System.ComponentModel.Win32Exception w32ex) when(w32ex.NativeErrorCode == ErrorFileNotFound)
            {
                throw new ProcessNotFoundException(processInfo.FileName, w32ex);
            }
        }
Exemplo n.º 17
0
        public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, Dictionary<Account, AccountState> accounts, BlockIdentity chainTip)
        {
            if (accounts == null)
                throw new ArgumentNullException(nameof(accounts));
            if (chainTip == null)
                throw new ArgumentNullException(nameof(chainTip));

            var totalBalance = accounts.Aggregate((Amount)0, (acc, kvp) => acc + kvp.Value.TotalBalance);

            _transactionCount = txSet.MinedTransactions.Aggregate(0, (acc, b) => acc + b.Transactions.Count) +
                txSet.UnminedTransactions.Count;
            _accounts = accounts;

            ActiveChain = activeChain;
            RecentTransactions = txSet;
            TotalBalance = totalBalance;
            ChainTip = chainTip;
        }
Exemplo n.º 18
0
        public static Process Start(BlockChainIdentity intendedNetwork, string appDataDirectory)
        {
            if (intendedNetwork == null)
            {
                throw new ArgumentNullException(nameof(intendedNetwork));
            }
            if (appDataDirectory == null)
            {
                throw new ArgumentNullException(nameof(appDataDirectory));
            }

            var networkFlag = "";

            if (intendedNetwork == BlockChainIdentity.TestNet3)
            {
                // While the actual name of the network is "testnet3", the wallet uses
                // the --testnet flag for this network.
                networkFlag = "--testnet";
            }
            else if (intendedNetwork != BlockChainIdentity.MainNet)
            {
                networkFlag = $"--{intendedNetwork.Name}";
            }

            var v4ListenAddress = RpcListenAddress("127.0.0.1", intendedNetwork);

            var processInfo = new ProcessStartInfo();

            processInfo.FileName               = "btcwallet";
            processInfo.Arguments              = $"{networkFlag} --noinitialload --experimentalrpclisten={v4ListenAddress} --onetimetlskey --datadir=\"{appDataDirectory}\"";
            processInfo.UseShellExecute        = false;
            processInfo.RedirectStandardError  = true;
            processInfo.RedirectStandardOutput = true;
            processInfo.CreateNoWindow         = true;

            var process = Process.Start(processInfo);

            process.ErrorDataReceived  += (sender, args) => Console.WriteLine("err> {0}", args.Data);
            process.OutputDataReceived += (sender, args) => Console.WriteLine("{0}", args.Data);
            process.BeginErrorReadLine();
            process.BeginOutputReadLine();
            return(process);
        }
Exemplo n.º 19
0
        public static bool TryFromOutputScript(OutputScript pkScript, BlockChainIdentity intendedBlockChain, out Address address)
        {
            var payToPubKeyHashScript = pkScript as OutputScript.PubKeyHash;

            if (payToPubKeyHashScript != null)
            {
                address = new PayToPubKeyHash(intendedBlockChain, payToPubKeyHashScript.Hash160);
                return(true);
            }

            var payToScriptHashScript = pkScript as OutputScript.ScriptHash;

            if (payToScriptHashScript != null)
            {
                address = new PayToScriptHash(intendedBlockChain, payToScriptHashScript.Hash160);
                return(true);
            }

            address = null;
            return(false);
        }
Exemplo n.º 20
0
        public static void ValidPayToPubKeyHashAddresses(string encodedAddress, BlockChainIdentity intendedBlockChain)
        {
            // Assert TryDecode and Decode succeed on valid address.
            Address address;

            Assert.True(Address.TryDecode(encodedAddress, out address));
            address = Address.Decode(encodedAddress);

            // Assert actual instance type is PayToPubKeyHash
            Assert.IsType <Address.PayToPubKeyHash>(address);
            var p2pkhAddress = (Address.PayToPubKeyHash)address;

            // Assert address is for the correct intended network.
            Assert.Same(address.IntendedBlockChain, intendedBlockChain);

            // Assert reencoded address string is equal to the test input.
            var newAddress       = new Address.PayToPubKeyHash(intendedBlockChain, p2pkhAddress.PubKeyHash);
            var reencodedAddress = newAddress.Encode();

            Assert.Equal(encodedAddress, reencodedAddress);
        }
Exemplo n.º 21
0
 public static bool IdentityForScriptHashPrefix(AddressPrefix prefix, out BlockChainIdentity identity)
 {
     if (prefix == MainNetPayToScriptHashPrefix)
     {
         identity = BlockChainIdentity.MainNet;
         return true;
     }
     else if (prefix == TestNet3PayToScriptHashPrefix)
     {
         identity = BlockChainIdentity.TestNet3;
         return true;
     }
     else if (prefix == SimNetPayToScriptHashPrefix)
     {
         identity = BlockChainIdentity.SimNet;
         return true;
     }
     else
     {
         identity = null;
         return false;
     }
 }
Exemplo n.º 22
0
 public static bool IdentityForPubkeyHashPrefix(AddressPrefix prefix, out BlockChainIdentity identity)
 {
     if (prefix == MainNetPayToPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.MainNet;
         return(true);
     }
     else if (prefix == TestNet3PayToPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.TestNet3;
         return(true);
     }
     else if (prefix == SimNetPayToPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.SimNet;
         return(true);
     }
     else
     {
         identity = null;
         return(false);
     }
 }
Exemplo n.º 23
0
        public static AddressPrefix PayToScriptHashPrefix(BlockChainIdentity identity)
        {
            if (identity == null)
            {
                throw new ArgumentNullException(nameof(identity));
            }

            if (identity == BlockChainIdentity.MainNet)
            {
                return(MainNetPayToScriptHashPrefix);
            }
            else if (identity == BlockChainIdentity.TestNet3)
            {
                return(TestNet3PayToScriptHashPrefix);
            }
            else if (identity == BlockChainIdentity.SimNet)
            {
                return(SimNetPayToScriptHashPrefix);
            }
            else
            {
                throw new UnknownBlockChainException($"Unknown blockchain `{identity.Name}`");
            }
        }
Exemplo n.º 24
0
        private const uint ImportedAccountNumber = 2147483647; // 2**31 - 1 

        public Wallet(BlockChainIdentity activeChain, TransactionSet txSet, List<AccountProperties> bip0032Accounts,
            AccountProperties importedAccount, BlockIdentity chainTip)
        {
            if (activeChain == null)
                throw new ArgumentNullException(nameof(activeChain));
            if (bip0032Accounts == null)
                throw new ArgumentNullException(nameof(bip0032Accounts));
            if (importedAccount == null)
                throw new ArgumentNullException(nameof(importedAccount));
            if (chainTip == null)
                throw new ArgumentNullException(nameof(chainTip));

            _transactionCount = txSet.MinedTransactions.Aggregate(0, (acc, b) => acc + b.Transactions.Count) +
                txSet.UnminedTransactions.Count;
            _bip0032Accounts = bip0032Accounts;
            _importedAccount = importedAccount;

            var totalBalance = EnumerateAccounts().Aggregate((Amount)0, (acc, a) => acc + a.Item2.TotalBalance);

            ActiveChain = activeChain;
            RecentTransactions = txSet;
            TotalBalance = totalBalance;
            ChainTip = chainTip;
        }
Exemplo n.º 25
0
        public static AddressPrefix PayToSecp256k1PubKeyHashPrefix(BlockChainIdentity identity)
        {
            if (identity == null)
            {
                throw new ArgumentNullException(nameof(identity));
            }

            if (identity == BlockChainIdentity.MainNet)
            {
                return(MainNetSecp256k1PubKeyHashPrefix);
            }
            else if (identity == BlockChainIdentity.TestNet)
            {
                return(TestNetSecp256k1PubKeyHashPrefix);
            }
            else if (identity == BlockChainIdentity.SimNet)
            {
                return(SimNetSecp256k1PubKeyHashPrefix);
            }
            else
            {
                throw new UnknownBlockChainException(identity);
            }
        }
Exemplo n.º 26
0
 private ProcessArguments(BlockChainIdentity intendedNetwork, bool searchPathForWalletProcess)
 {
     IntendedNetwork        = intendedNetwork;
     SearchPathForProcesses = searchPathForWalletProcess;
 }
Exemplo n.º 27
0
 private ProcessArguments(BlockChainIdentity intendedNetwork)
 {
     IntendedNetwork = intendedNetwork;
 }
Exemplo n.º 28
0
 private ProcessArguments(BlockChainIdentity intendedNetwork, bool searchPathForWalletProcess, string extraWalletArgs)
 {
     IntendedNetwork            = intendedNetwork;
     SearchPathForWalletProcess = searchPathForWalletProcess;
     ExtraWalletArgs            = extraWalletArgs;
 }
Exemplo n.º 29
0
 /// <summary>
 /// The number of recent blocks which are always kept in memory in a synced wallet.
 /// Transactions from these recent blocks are used to subtract output amounts from
 /// the total balance to calculate spendable balances given some number of confirmations.
 /// This value may not be less than the coinbase maturity level, or immature coinbase
 /// outputs will not be subtracted from the spendable balance.
 /// </summary>
 public static int NumRecentBlocks(BlockChainIdentity blockChain) => blockChain.Maturity;
Exemplo n.º 30
0
        public static bool TryFromOutputScript(OutputScript pkScript, BlockChainIdentity intendedBlockChain, out Address address)
        {
            var payToPubKeyHashScript = pkScript as OutputScript.Secp256k1PubKeyHash;
            if (payToPubKeyHashScript != null)
            {
                address = new PayToSecp256k1PubKeyHash(intendedBlockChain, payToPubKeyHashScript.Hash160);
                return true;
            }

            var payToEd25519PubKeyHashScript = pkScript as OutputScript.Ed25519PubKeyHash;
            if (payToEd25519PubKeyHashScript != null)
            {
                address = new PayToEd25519PubKeyHash(intendedBlockChain, payToEd25519PubKeyHashScript.Hash160);
                return true;
            }

            var payToSecSchnorrPubKeyHash = pkScript as OutputScript.SecSchnorrPubKeyHash;
            if (payToSecSchnorrPubKeyHash != null)
            {
                address = new PayToSecSchnorrPubKeyHash(intendedBlockChain, payToSecSchnorrPubKeyHash.Hash160);
                return true;
            }

            var payToScriptHashScript = pkScript as OutputScript.ScriptHash;
            if (payToScriptHashScript != null)
            {
                address = new PayToScriptHash(intendedBlockChain, payToScriptHashScript.Hash160);
                return true;
            }

            address = null;
            return false;
        }
Exemplo n.º 31
0
 public static bool IdentityForSecSchnorrPubKeyHashPrefix(AddressPrefix prefix, out BlockChainIdentity identity)
 {
     if (prefix == MainNetSchnorrPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.MainNet;
         return true;
     }
     else if (prefix == TestNetSchnorrPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.TestNet;
         return true;
     }
     else if (prefix == SimNetSchnorrPubKeyHashPrefix)
     {
         identity = BlockChainIdentity.SimNet;
         return true;
     }
     else
     {
         identity = null;
         return false;
     }
 }
Exemplo n.º 32
0
        /// <summary>
        /// Begins synchronization of the client with the remote wallet process.
        /// A delegate must be passed to be connected to the wallet's ChangesProcessed event to avoid
        /// a race where additional notifications are processed in the sync task before the caller
        /// can connect the event.  The caller is responsible for disconnecting the delegate from the
        /// event handler when finished.
        /// </summary>
        /// <param name="walletEventHandler">Event handler for changes to wallet as new transactions are processed.</param>
        /// <returns>The synced Wallet and the Task that is keeping the wallet in sync.</returns>
        public async Task <Tuple <Wallet, Task> > Synchronize(EventHandler <Wallet.ChangesProcessedEventArgs> walletEventHandler)
        {
            if (walletEventHandler == null)
            {
                throw new ArgumentNullException(nameof(walletEventHandler));
            }

            TransactionNotifications notifications;
            Task notificationsTask;

            // TODO: Initialization requests need timeouts.

            // Loop until synchronization did not race on a reorg.
            while (true)
            {
                // Begin receiving notifications for new and removed wallet transactions before
                // old transactions are downloaded.  Any received notifications are saved to
                // a buffer and are processed after GetAllTransactionsAsync is awaited.
                notifications     = new TransactionNotifications(_channel, _tokenSource.Token);
                notificationsTask = notifications.ListenAndBuffer();

                var networkTask  = NetworkAsync();
                var accountsTask = AccountsAsync();

                var networkResp      = await networkTask;
                var activeBlockChain = BlockChainIdentity.FromNetworkBits(networkResp.ActiveNetwork);

                var txSetTask = GetTransactionsAsync(Wallet.MinRecentTransactions, Wallet.NumRecentBlocks(activeBlockChain));

                var txSet       = await txSetTask;
                var rpcAccounts = await accountsTask;

                var lastAccountBlockHeight = rpcAccounts.CurrentBlockHeight;
                var lastAccountBlockHash   = new Blake256Hash(rpcAccounts.CurrentBlockHash.ToByteArray());
                var lastTxBlock            = txSet.MinedTransactions.LastOrDefault();
                if (lastTxBlock != null)
                {
                    var lastTxBlockHeight = lastTxBlock.Height;
                    var lastTxBlockHash   = lastTxBlock.Hash;
                    if (lastTxBlockHeight > lastAccountBlockHeight ||
                        (lastTxBlockHeight == lastAccountBlockHeight && !lastTxBlockHash.Equals(lastAccountBlockHash)))
                    {
                        _tokenSource.Cancel();
                        continue;
                    }
                }

                // Read all received notifications thus far and determine if synchronization raced
                // on a chain reorganize.  Try again if so.
                IList <WalletChanges> transactionNotifications;
                if (notifications.Buffer.TryReceiveAll(out transactionNotifications))
                {
                    if (transactionNotifications.Any(r => r.DetachedBlocks.Count != 0))
                    {
                        _tokenSource.Cancel();
                        continue;
                    }

                    // Skip all attached block notifications that are in blocks lower than the
                    // block accounts notification.  If blocks exist at or past that height,
                    // the first's hash should equal that from the accounts notification.
                    //
                    // This ensures that both notifications contain data that is valid at this
                    // block.
                    var remainingNotifications = transactionNotifications
                                                 .SelectMany(r => r.AttachedBlocks)
                                                 .SkipWhile(b => b.Height < lastAccountBlockHeight)
                                                 .ToList();
                    if (remainingNotifications.Count != 0)
                    {
                        if (!remainingNotifications[0].Hash.Equals(lastAccountBlockHash))
                        {
                            _tokenSource.Cancel();
                            continue;
                        }
                    }

                    // TODO: Merge remaining notifications with the transaction set.
                    // For now, be lazy and start the whole sync over.
                    if (remainingNotifications.Count > 1)
                    {
                        _tokenSource.Cancel();
                        continue;
                    }
                }

                var accounts = rpcAccounts.Accounts.ToDictionary(
                    a => new Account(a.AccountNumber),
                    a => new AccountProperties
                {
                    AccountName  = a.AccountName,
                    TotalBalance = a.TotalBalance,
                    // TODO: uncomment when added to protospec and implemented by wallet.
                    //ImmatureCoinbaseReward = a.ImmatureBalance,
                    ExternalKeyCount = a.ExternalKeyCount,
                    InternalKeyCount = a.InternalKeyCount,
                    ImportedKeyCount = a.ImportedKeyCount,
                });
                Func <AccountsResponse.Types.Account, AccountProperties> createProperties = a => new AccountProperties
                {
                    AccountName  = a.AccountName,
                    TotalBalance = a.TotalBalance,
                    // TODO: uncomment when added to protospec and implemented by wallet.
                    //ImmatureCoinbaseReward = a.ImmatureBalance,
                    ExternalKeyCount = a.ExternalKeyCount,
                    InternalKeyCount = a.InternalKeyCount,
                    ImportedKeyCount = a.ImportedKeyCount,
                };
                // This assumes that all but the last account listed in the RPC response are
                // BIP0032 accounts, with the same account number as their List index.
                var bip0032Accounts = rpcAccounts.Accounts.Take(rpcAccounts.Accounts.Count - 1).Select(createProperties).ToList();
                var importedAccount = createProperties(rpcAccounts.Accounts.Last());
                var chainTip        = new BlockIdentity(lastAccountBlockHash, lastAccountBlockHeight);
                var wallet          = new Wallet(activeBlockChain, txSet, bip0032Accounts, importedAccount, chainTip);
                wallet.ChangesProcessed += walletEventHandler;

                var syncTask = Task.Run(async() =>
                {
                    var client             = WalletService.NewClient(_channel);
                    var accountsStream     = client.AccountNotifications(new AccountNotificationsRequest(), cancellationToken: _tokenSource.Token);
                    var accountChangesTask = accountsStream.ResponseStream.MoveNext();
                    var txChangesTask      = notifications.Buffer.OutputAvailableAsync();
                    while (true)
                    {
                        var completedTask = await Task.WhenAny(accountChangesTask, txChangesTask);
                        if (!await completedTask)
                        {
                            break;
                        }
                        if (completedTask == accountChangesTask)
                        {
                            var accountProperties = accountsStream.ResponseStream.Current;
                            var account           = new Account(accountProperties.AccountNumber);
                            wallet.UpdateAccountProperties(account, accountProperties.AccountName,
                                                           accountProperties.ExternalKeyCount, accountProperties.InternalKeyCount,
                                                           accountProperties.ImportedKeyCount);
                            accountChangesTask = accountsStream.ResponseStream.MoveNext();
                        }
                        else if (completedTask == txChangesTask)
                        {
                            var changes = notifications.Buffer.Receive();
                            wallet.ApplyTransactionChanges(changes);
                        }
                    }

                    await notificationsTask;
                });

                return(Tuple.Create(wallet, syncTask));
            }
        }
Exemplo n.º 33
0
 /// <summary>
 /// The number of recent blocks which are always kept in memory in a synced wallet.
 /// Transactions from these recent blocks are used to subtract output amounts from
 /// the total balance to calculate spendable balances given some number of confirmations.
 /// This value may not be less than the coinbase maturity level, or immature coinbase
 /// outputs will not be subtracted from the spendable balance.
 /// </summary>
 public static int NumRecentBlocks(BlockChainIdentity blockChain) => blockChain.Maturity;
Exemplo n.º 34
0
 private Address(BlockChainIdentity intendedBlockChain)
 {
     IntendedBlockChain = intendedBlockChain;
 }
Exemplo n.º 35
0
 private Address(BlockChainIdentity intendedBlockChain)
 {
     IntendedBlockChain = intendedBlockChain;
 }