// accountIsUsed checks if an account has ever been used by scanning the // first acctSeekWidth many addresses for usage. func (w *Wallet) accountIsUsed(ctx *discoveryContext, account uint32) (bool, error) { 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 = ctx.deriveAddr(addrmgrNs, i, account, branch) return err }) // Skip erroneous keys, which happen rarely. if e, ok := err.(waddrmgr.ManagerError); ok && e.Err == hdkeychain.ErrInvalidChild { continue } if err != nil { return false, err } exists, err := ctx.chainClient.ExistsAddress(addr) if err != nil { return false, err } if exists { return true, nil } } } return false, nil }
// RescanFromHeight is an alternative to Rescan that takes a block height // instead of a hash. See Rescan for more details. func (w *Wallet) RescanFromHeight(chainClient *chain.RPCClient, startHeight int32) <-chan error { errc := make(chan error) go func() (err error) { defer func() { select { case errc <- err: default: if err != nil { log.Errorf("Rescan failed: %v", err) } close(errc) } }() var startHash chainhash.Hash err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error startHash, err = w.TxStore.GetMainChainBlockHashForHeight( txmgrNs, startHeight) return err }) if err != nil { return err } return w.rescan(chainClient, &startHash, startHeight, nil, nil) }() return errc }
// Rescan starts a rescan of the wallet for all blocks on the main chain // beginning at startHash. // // An error channel is returned for consumers of this API, but it is not // required to be read. If the error can not be immediately written to the // returned channel, the error will be logged and the channel will be closed. func (w *Wallet) Rescan(chainClient *chain.RPCClient, startHash *chainhash.Hash) <-chan error { errc := make(chan error) go func() (err error) { defer func() { select { case errc <- err: default: if err != nil { log.Errorf("Rescan failed: %v", err) } close(errc) } }() var startHeight int32 err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) header, err := w.TxStore.GetSerializedBlockHeader(txmgrNs, startHash) if err != nil { return err } startHeight = wtxmgr.ExtractBlockHeaderHeight(header) return nil }) if err != nil { return err } return w.rescan(chainClient, startHash, startHeight, nil, nil) }() return errc }
// TicketHashesForVotingAddress returns the hashes of all tickets with voting // rights delegated to votingAddr. This function does not return the hashes of // pruned tickets. func (w *Wallet) TicketHashesForVotingAddress(votingAddr dcrutil.Address) ([]chainhash.Hash, error) { var ticketHashes []chainhash.Hash err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error ticketHashes, err = w.StakeMgr.DumpSStxHashesForAddress( stakemgrNs, votingAddr) if err != nil { return err } // Exclude the hash if the transaction is not saved too. No // promises of hash order are given (and at time of writing, // they are copies of iterators of a Go map in wstakemgr) so // when one must be removed, replace it with the last and // decrease the len. for i := 0; i < len(ticketHashes); { if w.TxStore.ExistsTx(txmgrNs, &ticketHashes[i]) { i++ continue } ticketHashes[i] = ticketHashes[len(ticketHashes)-1] ticketHashes = ticketHashes[:len(ticketHashes)-1] } return nil }) return ticketHashes, err }
// FetchP2SHMultiSigOutput fetches information regarding a wallet's P2SH // multi-signature output. func (w *Wallet) FetchP2SHMultiSigOutput(outPoint *wire.OutPoint) (*P2SHMultiSigOutput, error) { var ( mso *wtxmgr.MultisigOut redeemScript []byte ) err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error mso, err = w.TxStore.GetMultisigOutput(txmgrNs, outPoint) if err != nil { return err } redeemScript, err = w.TxStore.GetTxScript(txmgrNs, mso.ScriptHash[:]) if err != nil { return err } // returns nil, nil when it successfully found no script. That error is // only used to return early when the database is closed. if redeemScript == nil { return errors.New("script not found") } return nil }) if err != nil { return nil, err } p2shAddr, err := dcrutil.NewAddressScriptHashFromHash( mso.ScriptHash[:], w.chainParams) if err != nil { return nil, err } multiSigOutput := P2SHMultiSigOutput{ OutPoint: *mso.OutPoint, OutputAmount: mso.Amount, ContainingBlock: BlockIdentity{ Hash: mso.BlockHash, Height: int32(mso.BlockHeight), }, P2SHAddress: p2shAddr, RedeemScript: redeemScript, M: mso.M, N: mso.N, Redeemer: nil, } if mso.Spent { multiSigOutput.Redeemer = &OutputRedeemer{ TxHash: mso.SpentBy, InputIndex: mso.SpentByIndex, } } return &multiSigOutput, nil }
// bisectLastAddrIndex is a helper function for search through addresses. func (w *Wallet) bisectLastAddrIndex(hi, low int, account uint32, branch uint32) int { chainClient, err := w.requireChainClient() if err != nil { return 0 } // Logarithmically scan address indexes to find the last used // address index. Each time the algorithm receives an end point, // scans a chunk of addresses at the end point, and if no // addresses are found, divides the address index by two and // repeats until it finds the last used index. offset := low for i := hi - low - 1; i > 0; i /= 2 { if i+offset+int(addrSeekWidth) < waddrmgr.MaxAddressesPerAccount { start := i + offset end := i + offset + int(addrSeekWidth) exists, idx, err := w.scanAddressRange(account, branch, start, end, chainClient) // Skip erroneous keys, which happen rarely. Don't skip // other errors. if err == errDerivation { continue } if err != nil { log.Warnf("unexpected error encountered during bisection "+ "scan of account %v, branch %v: %s", account, branch, err.Error()) return 0 } if exists { return idx } } else { 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, uint32(i+offset), account, branch) return err }) // Skip erroneous keys, which happen rarely. if err != nil { continue } exists, err := chainClient.ExistsAddress(addr) if err != nil { return 0 } if exists { return i + offset } } } return 0 }
// AddressPoolIndex returns the next to use address index for the passed // branch of the passed account. func (w *Wallet) AddressPoolIndex(account uint32, branch uint32) (uint32, error) { var index uint32 err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error index, err = w.addressPoolIndex(waddrmgrNs, account, branch) return err }) return index, err }
// TxDetails calls wtxmgr.Store.TxDetails under a single database view transaction. func (u unstableAPI) TxDetails(txHash *chainhash.Hash) (*wtxmgr.TxDetails, error) { var details *wtxmgr.TxDetails err := walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) var err error details, err = u.w.TxStore.TxDetails(txmgrNs, txHash) return err }) return details, err }
// FetchAllRedeemScripts returns all P2SH redeem scripts saved by the wallet. func (w *Wallet) FetchAllRedeemScripts() ([][]byte, error) { var redeemScripts [][]byte err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) var err error redeemScripts, err = w.TxStore.StoredTxScripts(txmgrNs) return err }) return redeemScripts, err }
// UnspentMultisigCreditsForAddress calls // wtxmgr.Store.UnspentMultisigCreditsForAddress under a single database view // transaction. func (u unstableAPI) UnspentMultisigCreditsForAddress(p2shAddr *dcrutil.AddressScriptHash) ([]*wtxmgr.MultisigCredit, error) { var multisigCredits []*wtxmgr.MultisigCredit err := walletdb.View(u.w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error multisigCredits, err = u.w.TxStore.UnspentMultisigCreditsForAddress( txmgrNs, p2shAddr) return err }) return multisigCredits, 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 }
// bisectLastAddrIndex is a helper function for search through addresses. func (w *Wallet) bisectLastAddrIndex(ctx *discoveryContext, hi, low uint32, account uint32, branch uint32) (uint32, error) { // Logarithmically scan address indexes to find the last used // address index. Each time the algorithm receives an end point, // scans a chunk of addresses at the end point, and if no // addresses are found, divides the address index by two and // repeats until it finds the last used index. offset := low for i := hi - low - 1; i > 0; i /= 2 { if i+offset+addrSeekWidth < waddrmgr.MaxAddressesPerAccount { start := i + offset end := i + offset + addrSeekWidth exists, idx, err := w.scanAddressRange(ctx, account, branch, start, end) // Skip erroneous keys, which happen rarely. Don't skip // other errors. if e, ok := err.(waddrmgr.ManagerError); ok && e.Err == hdkeychain.ErrInvalidChild { continue } if err != nil { return 0, err } if exists { return idx, nil } } else { 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+offset, account, branch) return err }) // Skip erroneous keys, which happen rarely. if err != nil { continue } exists, err := ctx.chainClient.ExistsAddress(addr) if err != nil { return 0, err } if exists { return i + offset, nil } } } return 0, nil }
// VoteBitsForTicket returns the per-ticket vote bits, if any are saved, falling // back to the wallet's default vote bits when missing. func (w *Wallet) VoteBitsForTicket(ticketHash *chainhash.Hash) (stake.VoteBits, error) { var voteBits stake.VoteBits var ok bool err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey) var err error ok, voteBits, err = w.StakeMgr.SStxVoteBits(stakemgrNs, ticketHash) return err }) if !ok { voteBits = w.VoteBits } return voteBits, err }
// StakePoolUserInfo returns the stake pool user information for a user // identified by their P2SH voting address. func (w *Wallet) StakePoolUserInfo(userAddress dcrutil.Address) (*wstakemgr.StakePoolUser, error) { switch userAddress.(type) { case *dcrutil.AddressPubKeyHash: // ok case *dcrutil.AddressScriptHash: // ok default: return nil, errors.New("stake pool user address must be P2PKH or P2SH") } var user *wstakemgr.StakePoolUser err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { stakemgrNs := tx.ReadBucket(wstakemgrNamespaceKey) var err error user, err = w.StakeMgr.StakePoolUserInfo(stakemgrNs, userAddress) return err }) return user, err }
// RescanProgressFromHeight rescans for relevant transactions in all blocks in // the main chain starting at startHeight. Progress notifications and any // errors are sent to the channel p. This function blocks until the rescan // completes or ends in an error. p is closed before returning. func (w *Wallet) RescanProgressFromHeight(chainClient *chain.RPCClient, startHeight int32, p chan<- RescanProgress, cancel <-chan struct{}) { defer close(p) var startHash chainhash.Hash err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error startHash, err = w.TxStore.GetMainChainBlockHashForHeight( txmgrNs, startHeight) return err }) if err != nil { p <- RescanProgress{Err: err} return } err = w.rescan(chainClient, &startHash, startHeight, p, cancel) if err != nil { p <- RescanProgress{Err: err} } }
// 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 }
// RangeTransactions calls wtxmgr.Store.RangeTransactions under a single // database view tranasction. func (u unstableAPI) RangeTransactions(begin, end int32, f func([]wtxmgr.TxDetails) (bool, error)) error { return walletdb.View(u.w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) return u.w.TxStore.RangeTransactions(txmgrNs, begin, end, f) }) }
// 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 } }
// UnspentOutputs fetches all unspent outputs from the wallet that match rules // described in the passed policy. func (w *Wallet) UnspentOutputs(policy OutputSelectionPolicy) ([]*TransactionOutput, error) { var outputResults []*TransactionOutput err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) _, tipHeight := w.TxStore.MainChainTip(txmgrNs) // TODO: actually stream outputs from the db instead of fetching // all of them at once. outputs, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } for _, output := range outputs { // Ignore outputs that haven't reached the required // number of confirmations. if !policy.meetsRequiredConfs(output.Height, tipHeight) { continue } // Ignore outputs that are not controlled by the account. _, addrs, _, err := txscript.ExtractPkScriptAddrs( txscript.DefaultScriptVersion, output.PkScript, w.chainParams) if err != nil || len(addrs) == 0 { // Cannot determine which account this belongs // to without a valid address. TODO: Fix this // by saving outputs per account, or accounts // per output. continue } outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err != nil { return err } if outputAcct != policy.Account { continue } // Stakebase isn't exposed by wtxmgr so those will be // OutputKindNormal for now. outputSource := OutputKindNormal if output.FromCoinBase { outputSource = OutputKindCoinbase } result := &TransactionOutput{ OutPoint: output.OutPoint, Output: wire.TxOut{ Value: int64(output.Amount), // TODO: version is bogus but there is // only version 0 at time of writing. Version: txscript.DefaultScriptVersion, PkScript: output.PkScript, }, OutputKind: outputSource, ContainingBlock: BlockIdentity(output.Block), ReceiveTime: output.Received, } outputResults = append(outputResults, result) } return nil }) return outputResults, err }
// scanAddressIndex identifies the last used address in an HD keychain of public // keys. It returns the index of the last used key, along with the address of // this key. func (w *Wallet) scanAddressIndex(ctx *discoveryContext, start, end uint32, account uint32, branch uint32) (uint32, dcrutil.Address, error) { // Find the last used address. Scan from it to the end in case there was a // gap from that position, which is possible. Then, return the address // in that position. lastUsed, err := w.findAddrEnd(ctx, start, end, account, branch) if err != nil { return 0, nil, err } // If debug is on, do an exhaustive check and a graphical printout // of what the used addresses currently look like. if log.Level() <= btclog.DebugLvl { dbgStr, err := debugAccountAddrGapsString(ctx.chainClient, lastUsed+debugAddrScanLength, account, branch, w) if err != nil { log.Debugf("Failed to debug address gaps for account %v, "+ "branch %v: %v", account, branch, err) } else { log.Debugf("%v", dbgStr) } } // If there was a last used index, do an exhaustive final scan that // reexamines the last used addresses and ensures that the final index // we have found is correct. if lastUsed != 0 { start := lastUsed end := lastUsed + uint32(w.addrIdxScanLen) exists, idx, err := w.scanAddressRange(ctx, account, branch, start, end) if err != nil { return 0, nil, err } if exists { lastUsed = idx 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, lastUsed, account, branch) return err }) if err != nil { return 0, nil, err } return lastUsed, addr, nil } } // In the case that 0 was returned as the last used address, // make sure the the 0th address was not used. If it was, // return this address to let the caller know that this // 0th address was used. if lastUsed == 0 { 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, 0, account, branch) return err }) // Skip erroneous keys. if err != nil { return 0, nil, err } exists, err := ctx.chainClient.ExistsAddress(addr) if err != nil { return 0, nil, fmt.Errorf("failed to access chain server: %v", err) } if exists { return 0, addr, nil } } // We can't find any used addresses for this account's // branch. return 0, nil, nil }
// 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 }
// initialize initializes an address pool for the passed account and branch // to the address index given. It will automatically load a buffer of addresses // from the address manager to use for upcoming calls. func (a *addressPool) initialize(account uint32, branch uint32, index uint32, w *Wallet) error { a.mutex.Lock() defer a.mutex.Unlock() // Do not reinitialize an address pool that was already started. // This can happen if the RPC client dies due to a disconnect // from the daemon. if a.started { return nil } // 0 and 1 refer to the external and internal branches of the wallet. // Other branches are so far unused. if branch > waddrmgr.InternalBranch { return fmt.Errorf("unknown branch %v given when attempting to "+ "initialize address pool for account %v", branch, account) } // Access the manager and get the synced to index, then insert all // the unused addresses into the address pool. var mgrIdx uint32 err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { waddrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) lastAddrFunc := w.Manager.LastExternalAddress if branch == waddrmgr.InternalBranch { lastAddrFunc = w.Manager.LastInternalAddress } var err error _, mgrIdx, err = lastAddrFunc(waddrmgrNs, account) if err != nil { return fmt.Errorf("failed to retrieve the last used addr index "+ "from the address manager for branch %v, acct %v: %s", branch, account, err.Error()) } if mgrIdx < index { return fmt.Errorf("manager is out of sync with the passed index "+ "(index %v, mgr index %v)", index, mgrIdx) } if mgrIdx == index { a.addresses = make([]string, 0) } else { fetchNum := mgrIdx - index + 1 a.addresses = make([]string, fetchNum) for i := uint32(0); i < fetchNum; i++ { addr, err := w.Manager.AddressDerivedFromDbAcct(waddrmgrNs, index+i, account, branch) if err != nil { return fmt.Errorf("failed to get the address at index %v "+ "for account %v, branch %v: %s", index+i, account, branch, err.Error()) } a.addresses[i] = addr.EncodeAddress() } } return nil }) if err != nil { return fmt.Errorf("failed to initialize address pool: %v", err) } a.wallet = w a.account = account a.branch = branch a.index = index log.Debugf("Address pool for account %v initialized to next "+ "address index %v on branch %v", account, a.index, branch) log.Debugf("The address manager buffered address space is %v "+ "many addresses (manager index: %v) for account %v, branch %v", len(a.addresses), mgrIdx, account, branch) a.cursor = 0 a.started = true return nil }