func (w *Wallet) handleChainVotingNotifications(chainClient *chain.RPCClient) { for n := range chainClient.NotificationsVoting() { var err error strErrType := "" switch n := n.(type) { case chain.WinningTickets: err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.handleWinningTickets(dbtx, n.BlockHash, n.BlockHeight, n.Tickets) }) strErrType = "WinningTickets" case chain.MissedTickets: err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.handleMissedTickets(dbtx, n.BlockHash, n.BlockHeight, n.Tickets) }) strErrType = "MissedTickets" default: err = fmt.Errorf("voting handler received unknown ntfn type") } if err != nil { log.Errorf("Cannot handle chain server voting "+ "notification %v: %v", strErrType, err) } } w.wg.Done() }
func (w *Wallet) handleConsensusRPCNotifications(chainClient *chain.RPCClient) { for n := range chainClient.Notifications() { var notificationName string var err error switch n := n.(type) { case chain.ClientConnected: log.Infof("The client has successfully connected to dcrd and " + "is now handling websocket notifications") case chain.BlockConnected: notificationName = "blockconnected" err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { return w.onBlockConnected(tx, n.BlockHeader, n.Transactions) }) case chain.Reorganization: notificationName = "reorganizing" err = w.handleReorganizing(n.OldHash, n.NewHash, n.OldHeight, n.NewHeight) case chain.RelevantTxAccepted: notificationName = "relevanttxaccepted" err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { return w.processTransaction(dbtx, n.Transaction, nil, nil) }) } if err != nil { log.Errorf("Failed to process consensus server notification "+ "(name: `%s`, detail: `%v`)", notificationName, err) } } }
// scanAddressRange scans backwards from end to start many addresses in the // account branch, and return the first index that is found on the blockchain. // If the address doesn't exist, false is returned as the first argument. func (w *Wallet) scanAddressRange(account uint32, branch uint32, start int, end int, chainClient *chain.RPCClient) (bool, int, error) { var addresses []dcrutil.Address err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error addresses, err = w.Manager.AddressesDerivedFromDbAcct(addrmgrNs, uint32(start), uint32(end+1), account, branch) if err != nil { return errDerivation } return nil }) if err != nil { return false, 0, err } // Whether or not the addresses exist is encoded as a binary // bitset. exists, err := chainClient.ExistsAddresses(addresses) if err != nil { return false, 0, err } existsB, err := hex.DecodeString(exists) if err != nil { return false, 0, err } set := bitset.Bytes(existsB) // Prevent a panic when an empty message is passed as a response. if len(set) == 0 { return false, 0, nil } // Scan backwards and return if we find an address exists. idx := end itr := len(addresses) - 1 for idx >= start { // If the address exists in the mempool or blockchain according // to the bit set returned, return this index. if set.Get(itr) { return true, idx, nil } itr-- idx-- } return false, 0, nil }
// accountIsUsed checks if an account has ever been used by scanning the // first acctSeekWidth many addresses for usage. func (w *Wallet) accountIsUsed(account uint32, chainClient *chain.RPCClient) bool { // Search external branch then internal branch for a used // address. We need to set the address function to use based // on whether or not this is the initial sync. The function // AddressDerivedFromCointype is able to see addresses that // exists in accounts that have not yet been created, while // AddressDerivedFromDbAcct can not. addrFunc := w.Manager.AddressDerivedFromDbAcct if w.initiallyUnlocked { addrFunc = w.Manager.AddressDerivedFromCointype } for branch := uint32(0); branch < 2; branch++ { for i := uint32(0); i < acctSeekWidth; i++ { var addr dcrutil.Address err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error addr, err = addrFunc(addrmgrNs, i, account, branch) return err }) if err != nil { // Skip erroneous keys, which happen rarely. continue } exists, err := chainClient.ExistsAddress(addr) if err != nil { return false } if exists { return true } } } return false }
// debugAccountAddrGapsString is a debug function that prints a graphical outlook // of address usage to a string, from the perspective of the daemon. func debugAccountAddrGapsString(chainClient *chain.RPCClient, scanBackFrom uint32, account uint32, branch uint32, w *Wallet) (string, error) { var buf bytes.Buffer str := fmt.Sprintf("Begin debug address scan scanning backwards from "+ "idx %v, account %v, branch %v\n", scanBackFrom, account, branch) buf.WriteString(str) var firstUsedIndex uint32 for i := scanBackFrom; i > 0; i-- { var addr dcrutil.Address err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs, i, account, branch) return err }) // Skip erroneous keys. if err != nil { continue } exists, err := chainClient.ExistsAddress(addr) if err != nil { return "", fmt.Errorf("failed to access chain server: %v", err) } if exists { firstUsedIndex = i break } } str = fmt.Sprintf("Last used index found: %v\n", firstUsedIndex) buf.WriteString(str) var batchSize uint32 = 50 batches := (firstUsedIndex / batchSize) + 1 var lastBatchSize uint32 if firstUsedIndex%batchSize != 0 { lastBatchSize = firstUsedIndex - ((batches - 1) * batchSize) } for i := uint32(0); i < batches; i++ { str = fmt.Sprintf("%8v", i*batchSize) buf.WriteString(str) start := i * batchSize end := (i + 1) * batchSize if i == batches-1 { // Nothing to do because last batch empty. if lastBatchSize == 0 { break } end = (i*batchSize + lastBatchSize) + 1 } for j := start; j < end; j++ { if j%10 == 0 { buf.WriteString(" ") } char := "_" var addr dcrutil.Address err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error addr, err = w.Manager.AddressDerivedFromDbAcct(addrmgrNs, j, account, branch) return err }) if err != nil { char = "X" } exists, err := chainClient.ExistsAddress(addr) if err != nil { return "", fmt.Errorf("failed to access chain server: %v", err) } if exists { char = "#" } buf.WriteString(char) } buf.WriteString("\n") } return buf.String(), nil }
// LiveTicketHashes returns the hashes of live tickets that have been purchased // by the wallet. func (w *Wallet) LiveTicketHashes(rpcClient *chain.RPCClient, includeImmature bool) ([]chainhash.Hash, error) { // This was mostly copied from an older version of the legacy RPC server // implementation, hence the overall weirdness, inefficiencies, and the // direct dependency on the consensus server RPC client. var blk waddrmgr.BlockStamp var ticketHashes []chainhash.Hash var stakeMgrTickets []chainhash.Hash err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) blk = w.Manager.SyncedTo() // UnspentTickets collects all the tickets that pay out to a // public key hash for a public key owned by this wallet. var err error ticketHashes, err = w.TxStore.UnspentTickets(txmgrNs, blk.Height, includeImmature) if err != nil { return err } // Access the stake manager and see if there are any extra tickets // there. Likely they were either pruned because they failed to get // into the blockchain or they are P2SH for some script we own. stakeMgrTickets, err = w.StakeMgr.DumpSStxHashes() return err }) if err != nil { return nil, err } for _, h := range stakeMgrTickets { if sliceContainsHash(ticketHashes, h) { continue } // Get the raw transaction information from daemon and add // any relevant tickets. The ticket output is always the // zeroeth output. spent, err := rpcClient.GetTxOut(&h, 0, true) if err != nil { continue } // This returns nil if the output is spent. if spent == nil { continue } ticketTx, err := rpcClient.GetRawTransactionVerbose(&h) if err != nil { continue } txHeight := ticketTx.BlockHeight unconfirmed := (txHeight == 0) immature := (blk.Height-int32(txHeight) < int32(w.ChainParams().TicketMaturity)) if includeImmature { ticketHashes = append(ticketHashes, h) } else { if !(unconfirmed || immature) { ticketHashes = append(ticketHashes, h) } } } return ticketHashes, nil }
// rescan synchronously scans over all blocks on the main chain starting at // startHash and height up through the recorded main chain tip block. The // progress channel, if non-nil, is sent non-error progress notifications with // the heights the rescan has completed through, starting with the start height. func (w *Wallet) rescan(chainClient *chain.RPCClient, startHash *chainhash.Hash, height int32, p chan<- RescanProgress, cancel <-chan struct{}) error { blockHashStorage := make([]chainhash.Hash, maxBlocksPerRescan) rescanFrom := *startHash inclusive := true for { select { case <-cancel: return nil default: } var rescanBlocks []chainhash.Hash err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) var err error rescanBlocks, err = w.TxStore.GetMainChainBlockHashes(txmgrNs, &rescanFrom, inclusive, blockHashStorage) return err }) if err != nil { return err } if len(rescanBlocks) == 0 { return nil } scanningThrough := height + int32(len(rescanBlocks)) - 1 log.Infof("Rescanning blocks %v-%v...", height, scanningThrough) rescanResults, err := chainClient.Rescan(rescanBlocks) if err != nil { return err } var rawBlockHeader wtxmgr.RawBlockHeader err = walletdb.Update(w.db, func(dbtx walletdb.ReadWriteTx) error { txmgrNs := dbtx.ReadWriteBucket(wtxmgrNamespaceKey) for _, r := range rescanResults.DiscoveredData { blockHash, err := chainhash.NewHashFromStr(r.Hash) if err != nil { return err } blockMeta, err := w.TxStore.GetBlockMetaForHash(txmgrNs, blockHash) if err != nil { return err } serHeader, err := w.TxStore.GetSerializedBlockHeader(txmgrNs, blockHash) if err != nil { return err } err = copyHeaderSliceToArray(&rawBlockHeader, serHeader) if err != nil { return err } for _, hexTx := range r.Transactions { serTx, err := hex.DecodeString(hexTx) if err != nil { return err } err = w.processTransaction(dbtx, serTx, &rawBlockHeader, &blockMeta) if err != nil { return err } } } return nil }) if err != nil { return err } if p != nil { p <- RescanProgress{ScannedThrough: scanningThrough} } rescanFrom = rescanBlocks[len(rescanBlocks)-1] height += int32(len(rescanBlocks)) inclusive = false } }