Esempio n. 1
0
        public static void HasReferenceSemantics()
        {
            var firstArray = new byte[Blake256Hash.Length];
            var secondArray = new byte[Blake256Hash.Length];

            for (var i = 0; i < Blake256Hash.Length; i++)
            {
                firstArray[i] = (byte)i;
                secondArray[i] = (byte)i;
            }

            var firstHash = new Blake256Hash(firstArray);
            var secondHash = new Blake256Hash(secondArray);

            // Value equality
            Assert.Equal(firstHash, secondHash);
            Assert.True(firstHash.Equals(secondHash));
            Assert.Equal(firstHash.GetHashCode(), secondHash.GetHashCode());

            // Reference equality
            Assert.False(firstHash == secondHash);

            var hashSet = new HashSet<Blake256Hash> { firstHash };
            Assert.True(hashSet.Contains(secondHash));
        }
Esempio n. 2
0
        public WalletTransaction(Transaction transaction, Blake256Hash hash, Input[] inputs, Output[] outputs, Amount?fee, DateTimeOffset seenTime)
        {
            if (transaction == null)
            {
                throw new ArgumentNullException(nameof(transaction));
            }
            if (hash == null)
            {
                throw new ArgumentNullException(nameof(hash));
            }
            if (inputs == null)
            {
                throw new ArgumentNullException(nameof(inputs));
            }
            if (outputs == null)
            {
                throw new ArgumentNullException(nameof(outputs));
            }

            Hash        = hash;
            Inputs      = inputs;
            Outputs     = outputs;
            Fee         = fee;
            Transaction = transaction;
            SeenTime    = seenTime;
        }
Esempio n. 3
0
        public static void HasReferenceSemantics()
        {
            var firstArray  = new byte[Blake256Hash.Length];
            var secondArray = new byte[Blake256Hash.Length];

            for (var i = 0; i < Blake256Hash.Length; i++)
            {
                firstArray[i]  = (byte)i;
                secondArray[i] = (byte)i;
            }

            var firstHash  = new Blake256Hash(firstArray);
            var secondHash = new Blake256Hash(secondArray);

            // Value equality
            Assert.Equal(firstHash, secondHash);
            Assert.True(firstHash.Equals(secondHash));
            Assert.Equal(firstHash.GetHashCode(), secondHash.GetHashCode());

            // Reference equality
            Assert.False(firstHash == secondHash);

            var hashSet = new HashSet <Blake256Hash> {
                firstHash
            };

            Assert.True(hashSet.Contains(secondHash));
        }
Esempio n. 4
0
        public static Block MarshalBlock(BlockDetails b)
        {
            var hash = new Blake256Hash(b.Hash.ToByteArray());
            var height = b.Height;
            var unixTime = b.Timestamp;
            var transactions = b.Transactions.Select(MarshalWalletTransaction).ToList();

            return new Block(hash, height, unixTime, transactions);
        }
Esempio n. 5
0
        public static Block MarshalBlock(BlockDetails b)
        {
            var hash         = new Blake256Hash(b.Hash.ToByteArray());
            var height       = b.Height;
            var unixTime     = b.Timestamp;
            var transactions = b.Transactions.Select(MarshalWalletTransaction).ToList();

            return(new Block(hash, height, unixTime, transactions));
        }
Esempio n. 6
0
            public OutPoint(Blake256Hash hash, uint index, byte tree)
            {
                if (hash == null)
                    throw new ArgumentNullException(nameof(hash));

                Hash = hash;
                Index = index;
                Tree = tree;
            }
Esempio n. 7
0
        public static UnspentOutput MarshalUnspentOutput(FundTransactionResponse.Types.PreviousOutput o)
        {
            var txHash         = new Blake256Hash(o.TransactionHash.ToByteArray());
            var outputIndex    = o.OutputIndex;
            var amount         = (Amount)o.Amount;
            var pkScript       = OutputScript.ParseScript(o.PkScript.ToByteArray());
            var seenTime       = DateTimeOffsetExtras.FromUnixTimeSeconds(o.ReceiveTime);
            var isFromCoinbase = o.FromCoinbase;

            return(new UnspentOutput(txHash, outputIndex, amount, pkScript, seenTime, isFromCoinbase));
        }
Esempio n. 8
0
        public Block(Blake256Hash hash, int height, long unixTime, List<WalletTransaction> transactions)
        {
            if (hash == null)
                throw new ArgumentNullException(nameof(hash));
            if (transactions == null)
                throw new ArgumentNullException(nameof(transactions));

            Identity = new BlockIdentity(hash, height);
            Timestamp = DateTimeOffsetExtras.FromUnixTimeSeconds(unixTime);
            Transactions = transactions;
        }
Esempio n. 9
0
        public static UnspentOutput MarshalUnspentOutput(FundTransactionResponse.Types.PreviousOutput o)
        {
            var txHash = new Blake256Hash(o.TransactionHash.ToByteArray());
            var outputIndex = o.OutputIndex;
            var tree = (byte)o.Tree;
            var amount = (Amount)o.Amount;
            var pkScript = OutputScript.ParseScript(o.PkScript.ToByteArray());
            var seenTime = DateTimeOffsetExtras.FromUnixTimeSeconds(o.ReceiveTime);
            var isFromCoinbase = o.FromCoinbase;

            return new UnspentOutput(txHash, outputIndex, tree, amount, pkScript, seenTime, isFromCoinbase);
        }
Esempio n. 10
0
        public UnspentOutput(Blake256Hash txHash, uint outputIndex, byte tree, Amount amount, OutputScript pkScript, DateTimeOffset seenTime, bool isFromCoinbase)
        {
            if (txHash == null)
                throw new ArgumentNullException(nameof(txHash));
            if (pkScript == null)
                throw new ArgumentNullException(nameof(pkScript));

            TransactionHash = txHash;
            OutputIndex = outputIndex;
            Tree = tree;
            Amount = amount;
            PkScript = pkScript;
            SeenTime = seenTime;
            IsFromCoinbase = IsFromCoinbase;
        }
Esempio n. 11
0
        public Block(Blake256Hash hash, int height, long unixTime, List <WalletTransaction> transactions)
        {
            if (hash == null)
            {
                throw new ArgumentNullException(nameof(hash));
            }
            if (transactions == null)
            {
                throw new ArgumentNullException(nameof(transactions));
            }

            Identity     = new BlockIdentity(hash, height);
            Timestamp    = DateTimeOffsetExtras.FromUnixTimeSeconds(unixTime);
            Transactions = transactions;
        }
Esempio n. 12
0
        public WalletTransaction(Transaction transaction, Blake256Hash hash, Input[] inputs, Output[] outputs, Amount? fee, DateTimeOffset seenTime)
        {
            if (transaction == null)
                throw new ArgumentNullException(nameof(transaction));
            if (hash == null)
                throw new ArgumentNullException(nameof(hash));
            if (inputs == null)
                throw new ArgumentNullException(nameof(inputs));
            if (outputs == null)
                throw new ArgumentNullException(nameof(outputs));

            Hash = hash;
            Inputs = inputs;
            Outputs = outputs;
            Fee = fee;
            Transaction = transaction;
            SeenTime = seenTime;
        }
Esempio n. 13
0
        public UnspentOutput(Blake256Hash txHash, uint outputIndex, Amount amount, OutputScript pkScript, DateTimeOffset seenTime, bool isFromCoinbase)
        {
            if (txHash == null)
            {
                throw new ArgumentNullException(nameof(txHash));
            }
            if (pkScript == null)
            {
                throw new ArgumentNullException(nameof(pkScript));
            }

            TransactionHash = txHash;
            OutputIndex     = outputIndex;
            Amount          = amount;
            PkScript        = pkScript;
            SeenTime        = seenTime;
            IsFromCoinbase  = IsFromCoinbase;
        }
Esempio n. 14
0
        public static WalletTransaction MarshalWalletTransaction(TransactionDetails tx)
        {
            var transaction = Transaction.Deserialize(tx.Transaction.ToByteArray());
            var hash        = new Blake256Hash(tx.Hash.ToByteArray());
            var inputs      = tx.Debits
                              .Select(i => new WalletTransaction.Input(i.PreviousAmount, new Account(i.PreviousAccount)))
                              .ToArray();
            // There are two kinds of transactions to care about when choosing which outputs
            // should be created: transactions created by other wallets (inputs.Length == 0)
            // and those that spend controlled outputs from this wallet (inputs.Length != 0).
            // If the transaction was created by this wallet, then all outputs (both controlled
            // and uncontrolled) should be included.  Otherwise, uncontrolled outputs can be
            // ignored since they are not relevant (they could be change outputs for the other
            // wallet or outputs created for another unrelated wallet).
            var outputs = inputs.Length == 0
                ? tx.Credits.Select(credit => MarshalControlledOutput(credit, transaction.Outputs[credit.Index])).ToArray()
                : MarshalCombinedOutputs(transaction, tx.Credits);
            var fee      = inputs.Length == transaction.Inputs.Length ? (Amount?)tx.Fee : null;
            var seenTime = DateTimeOffsetExtras.FromUnixTimeSeconds(tx.Timestamp);

            return(new WalletTransaction(transaction, hash, inputs, outputs, fee, seenTime));
        }
Esempio n. 15
0
        public static WalletTransaction MarshalWalletTransaction(TransactionDetails tx)
        {
            var transaction = Transaction.Deserialize(tx.Transaction.ToByteArray());
            var hash = new Blake256Hash(tx.Hash.ToByteArray());
            var inputs = tx.Debits
                .Select(i => new WalletTransaction.Input(i.PreviousAmount, new Account(i.PreviousAccount)))
                .ToArray();
            // There are two kinds of transactions to care about when choosing which outputs
            // should be created: transactions created by other wallets (inputs.Length == 0)
            // and those that spend controlled outputs from this wallet (inputs.Length != 0).
            // If the transaction was created by this wallet, then all outputs (both controlled
            // and uncontrolled) should be included.  Otherwise, uncontrolled outputs can be
            // ignored since they are not relevant (they could be change outputs for the other
            // wallet or outputs created for another unrelated wallet).
            var outputs = inputs.Length == 0
                ? tx.Credits.Select((o, i) => MarshalControlledOutput(o, transaction.Outputs[i])).ToArray()
                : MarshalCombinedOutputs(transaction, tx.Credits);
            var fee = inputs.Length == transaction.Inputs.Length ? (Amount?)tx.Fee : null;
            var seenTime = DateTimeOffsetExtras.FromUnixTimeSeconds(tx.Timestamp);

            return new WalletTransaction(transaction, hash, inputs, outputs, fee, seenTime);
        }
Esempio n. 16
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<Mutex<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 walletMutex = new Mutex<Wallet>(wallet);

                var syncTask = Task.Run(async () =>
                {
                    var client = new WalletService.WalletServiceClient(_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;
                        }

                        using (var walletGuard = await walletMutex.LockAsync())
                        {
                            var w = walletGuard.Instance;
                            if (completedTask == accountChangesTask)
                            {
                                var accountProperties = accountsStream.ResponseStream.Current;
                                var account = new Account(accountProperties.AccountNumber);
                                w.UpdateAccountProperties(account, accountProperties.AccountName,
                                    accountProperties.ExternalKeyCount, accountProperties.InternalKeyCount,
                                    accountProperties.ImportedKeyCount);
                                accountChangesTask = accountsStream.ResponseStream.MoveNext();
                            }
                            else if (completedTask == txChangesTask)
                            {
                                var changes = notifications.Buffer.Receive();
                                w.ApplyTransactionChanges(changes);
                                txChangesTask = notifications.Buffer.OutputAvailableAsync();
                            }
                        }
                    }

                    await notificationsTask;
                });

                return Tuple.Create(walletMutex, syncTask);
            }
        }
Esempio n. 17
0
        public static Transaction Deserialize(byte[] rawTransaction)
        {
            if (rawTransaction == null)
                throw new ArgumentNullException(nameof(rawTransaction));

            int version;
            Input[] inputs;
            Output[] outputs;
            uint lockTime;
            uint expiry;

            var cursor = new ByteCursor(rawTransaction);

            try
            {
                version = cursor.ReadInt32();
                if (version != SupportedVersion)
                {
                    var reason = $"Unsupported transaction version `{version}`";
                    throw new EncodingException(typeof(Transaction), cursor, reason);
                }

                // Prefix deserialization
                var prefixInputCount = cursor.ReadCompact();
                if (prefixInputCount > TransactionRules.MaxInputs)
                {
                    var reason = $"Input count {prefixInputCount} exceeds maximum value {TransactionRules.MaxInputs}";
                    throw new EncodingException(typeof(Transaction), cursor, reason);
                }
                inputs = new Input[prefixInputCount];
                for (int i = 0; i < (int)prefixInputCount; i++)
                {
                    var previousHash = new Blake256Hash(cursor.ReadBytes(Blake256Hash.Length));
                    var previousIndex = cursor.ReadUInt32();
                    var previousTree = cursor.ReadByte();
                    var previousOutPoint = new OutPoint(previousHash, previousIndex, previousTree);
                    var sequence = cursor.ReadUInt32();
                    inputs[i] = Input.CreateFromPrefix(previousOutPoint, sequence);
                }
                var outputCount = cursor.ReadCompact();
                if (outputCount > TransactionRules.MaxOutputs)
                {
                    var reason = $"Output count {outputCount} exceeds maximum value {TransactionRules.MaxOutputs}";
                    throw new EncodingException(typeof(Transaction), cursor, reason);
                }
                outputs = new Output[outputCount];
                for (int i = 0; i < (int)outputCount; i++)
                {
                    var amount = (Amount)cursor.ReadInt64();
                    var outputVersion = cursor.ReadUInt16();
                    var pkScript = cursor.ReadVarBytes(TransactionRules.MaxPayload);
                    outputs[i] = new Output(amount, outputVersion, pkScript);
                }
                lockTime = cursor.ReadUInt32();
                expiry = cursor.ReadUInt32();

                // Witness deserialization
                var witnessInputCount = cursor.ReadCompact();
                if (witnessInputCount != prefixInputCount)
                {
                    var reason = $"Input counts in prefix and witness do not match ({prefixInputCount} != {witnessInputCount})";
                    throw new EncodingException(typeof(Transaction), cursor, reason);
                }
                for (int i = 0; i < (int)witnessInputCount; i++)
                {
                    var inputAmount = (Amount)cursor.ReadInt64();
                    var blockHeight = cursor.ReadUInt32();
                    var blockIndex = cursor.ReadUInt32();
                    var signatureScript = cursor.ReadVarBytes(TransactionRules.MaxPayload);
                    inputs[i] = Input.AddWitness(inputs[i], inputAmount, blockHeight, blockIndex, signatureScript);
                }
            }
            catch (Exception ex) when (!(ex is EncodingException))
            {
                throw new EncodingException(typeof(Transaction), cursor, ex);
            }

            return new Transaction(version, inputs, outputs, lockTime, expiry);
        }
Esempio n. 18
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));
            }
        }