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; }
private Address(BlockChainIdentity intendedBlockChain) { if (intendedBlockChain == null) throw new ArgumentNullException(nameof(intendedBlockChain)); IntendedBlockChain = intendedBlockChain; }
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}"); }
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}"); }
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; }
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)); }
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})"); }
private Address(BlockChainIdentity intendedBlockChain) { if (intendedBlockChain == null) { throw new ArgumentNullException(nameof(intendedBlockChain)); } IntendedBlockChain = intendedBlockChain; }
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); }
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}`"); }
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})"); } }
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)); }
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; }
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; } } }
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); }
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); } }
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; }
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); }
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); }
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); }
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; } }
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); } }
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}`"); } }
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; }
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); } }
private ProcessArguments(BlockChainIdentity intendedNetwork, bool searchPathForWalletProcess) { IntendedNetwork = intendedNetwork; SearchPathForProcesses = searchPathForWalletProcess; }
private ProcessArguments(BlockChainIdentity intendedNetwork) { IntendedNetwork = intendedNetwork; }
private ProcessArguments(BlockChainIdentity intendedNetwork, bool searchPathForWalletProcess, string extraWalletArgs) { IntendedNetwork = intendedNetwork; SearchPathForWalletProcess = searchPathForWalletProcess; ExtraWalletArgs = extraWalletArgs; }
/// <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;
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; }
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; } }
/// <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)); } }
/// <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;
private Address(BlockChainIdentity intendedBlockChain) { IntendedBlockChain = intendedBlockChain; }
private Address(BlockChainIdentity intendedBlockChain) { IntendedBlockChain = intendedBlockChain; }