// Copyright (c) 2013-2017 The btcsuite developers // Copyright (c) 2015-2016 The Decred developers // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package wallet import ( "bytes" "encoding/hex" "errors" "fmt" "sort" "sync" "time" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/wallet/txauthor" "github.com/btcsuite/btcwallet/wallet/txrules" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" ) const ( // InsecurePubPassphrase is the default outer encryption passphrase used // for public data (everything but private keys). Using a non-default // public passphrase can prevent an attacker without the public // passphrase from discovering all past and future wallet addresses if // they gain access to the wallet database. // // NOTE: at time of writing, public encryption only applies to public // data in the waddrmgr namespace. Transactions are not yet encrypted. InsecurePubPassphrase = "public" walletDbWatchingOnlyName = "wowallet.db" ) // ErrNotSynced describes an error where an operation cannot complete // due wallet being out of sync (and perhaps currently syncing with) // the remote chain server. var ErrNotSynced = errors.New("wallet is not synchronized with the chain server") // Namespace bucket keys. var ( waddrmgrNamespaceKey = []byte("waddrmgr") wtxmgrNamespaceKey = []byte("wtxmgr") ) // Wallet is a structure containing all the components for a // complete wallet. It contains the Armory-style key store // addresses and keys), type Wallet struct { publicPassphrase []byte // Data stores db walletdb.DB Manager *waddrmgr.Manager TxStore *wtxmgr.Store chainClient chain.Interface chainClientLock sync.Mutex chainClientSynced bool chainClientSyncMtx sync.Mutex lockedOutpoints map[wire.OutPoint]struct{} // Channels for rescan processing. Requests are added and merged with // any waiting requests, before being sent to another goroutine to // call the rescan RPC. rescanAddJob chan *RescanJob rescanBatch chan *rescanBatch rescanNotifications chan interface{} // From chain server rescanProgress chan *RescanProgressMsg rescanFinished chan *RescanFinishedMsg // Channel for transaction creation requests. createTxRequests chan createTxRequest // Channels for the manager locker. unlockRequests chan unlockRequest lockRequests chan struct{} holdUnlockRequests chan chan heldUnlock lockState chan bool changePassphrase chan changePassphraseRequest // Information for reorganization handling. reorganizingLock sync.Mutex reorganizeToHash chainhash.Hash reorganizing bool NtfnServer *NotificationServer chainParams *chaincfg.Params wg sync.WaitGroup started bool quit chan struct{} quitMu sync.Mutex } // Start starts the goroutines necessary to manage a wallet. func (w *Wallet) Start() { w.quitMu.Lock() select { case <-w.quit: // Restart the wallet goroutines after shutdown finishes. w.WaitForShutdown() w.quit = make(chan struct{}) default: // Ignore when the wallet is still running. if w.started { w.quitMu.Unlock() return } w.started = true } w.quitMu.Unlock() w.wg.Add(2) go w.txCreator() go w.walletLocker() } // SynchronizeRPC associates the wallet with the consensus RPC client, // synchronizes the wallet with the latest changes to the blockchain, and // continuously updates the wallet through RPC notifications. // // This method is unstable and will be removed when all syncing logic is moved // outside of the wallet package. func (w *Wallet) SynchronizeRPC(chainClient chain.Interface) { w.quitMu.Lock() select { case <-w.quit: w.quitMu.Unlock() return default: } w.quitMu.Unlock() // TODO: Ignoring the new client when one is already set breaks callers // who are replacing the client, perhaps after a disconnect. w.chainClientLock.Lock() if w.chainClient != nil { w.chainClientLock.Unlock() return } w.chainClient = chainClient // If the chain client is a NeutrinoClient instance, set a birthday so // we don't download all the filters as we go. switch cc := chainClient.(type) { case *chain.NeutrinoClient: cc.SetStartTime(w.Manager.Birthday()) case *chain.BitcoindClient: cc.SetStartTime(w.Manager.Birthday()) } w.chainClientLock.Unlock() // TODO: It would be preferable to either run these goroutines // separately from the wallet (use wallet mutator functions to // make changes from the RPC client) and not have to stop and // restart them each time the client disconnects and reconnets. w.wg.Add(4) go w.handleChainNotifications() go w.rescanBatchHandler() go w.rescanProgressHandler() go w.rescanRPCHandler() } // requireChainClient marks that a wallet method can only be completed when the // consensus RPC server is set. This function and all functions that call it // are unstable and will need to be moved when the syncing code is moved out of // the wallet. func (w *Wallet) requireChainClient() (chain.Interface, error) { w.chainClientLock.Lock() chainClient := w.chainClient w.chainClientLock.Unlock() if chainClient == nil { return nil, errors.New("blockchain RPC is inactive") } return chainClient, nil } // ChainClient returns the optional consensus RPC client associated with the // wallet. // // This function is unstable and will be removed once sync logic is moved out of // the wallet. func (w *Wallet) ChainClient() chain.Interface { w.chainClientLock.Lock() chainClient := w.chainClient w.chainClientLock.Unlock() return chainClient } // quitChan atomically reads the quit channel. func (w *Wallet) quitChan() <-chan struct{} { w.quitMu.Lock() c := w.quit w.quitMu.Unlock() return c } // Stop signals all wallet goroutines to shutdown. func (w *Wallet) Stop() { w.quitMu.Lock() quit := w.quit w.quitMu.Unlock() select { case <-quit: default: close(quit) w.chainClientLock.Lock() if w.chainClient != nil { w.chainClient.Stop() w.chainClient = nil } w.chainClientLock.Unlock() } } // ShuttingDown returns whether the wallet is currently in the process of // shutting down or not. func (w *Wallet) ShuttingDown() bool { select { case <-w.quitChan(): return true default: return false } } // WaitForShutdown blocks until all wallet goroutines have finished executing. func (w *Wallet) WaitForShutdown() { w.chainClientLock.Lock() if w.chainClient != nil { w.chainClient.WaitForShutdown() } w.chainClientLock.Unlock() w.wg.Wait() } // SynchronizingToNetwork returns whether the wallet is currently synchronizing // with the Bitcoin network. func (w *Wallet) SynchronizingToNetwork() bool { // At the moment, RPC is the only synchronization method. In the // future, when SPV is added, a separate check will also be needed, or // SPV could always be enabled if RPC was not explicitly specified when // creating the wallet. w.chainClientSyncMtx.Lock() syncing := w.chainClient != nil w.chainClientSyncMtx.Unlock() return syncing } // ChainSynced returns whether the wallet has been attached to a chain server // and synced up to the best block on the main chain. func (w *Wallet) ChainSynced() bool { w.chainClientSyncMtx.Lock() synced := w.chainClientSynced w.chainClientSyncMtx.Unlock() return synced } // SetChainSynced marks whether the wallet is connected to and currently in sync // with the latest block notified by the chain server. // // NOTE: Due to an API limitation with rpcclient, this may return true after // the client disconnected (and is attempting a reconnect). This will be unknown // until the reconnect notification is received, at which point the wallet can be // marked out of sync again until after the next rescan completes. func (w *Wallet) SetChainSynced(synced bool) { w.chainClientSyncMtx.Lock() w.chainClientSynced = synced w.chainClientSyncMtx.Unlock() } // activeData returns the currently-active receiving addresses and all unspent // outputs. This is primarely intended to provide the parameters for a // rescan request. func (w *Wallet) activeData(dbtx walletdb.ReadTx) ([]btcutil.Address, []wtxmgr.Credit, error) { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) var addrs []btcutil.Address err := w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { addrs = append(addrs, addr) return nil }) if err != nil { return nil, nil, err } unspent, err := w.TxStore.UnspentOutputs(txmgrNs) return addrs, unspent, err } // syncWithChain brings the wallet up to date with the current chain server // connection. It creates a rescan request and blocks until the rescan has // finished. // func (w *Wallet) syncWithChain() error { chainClient, err := w.requireChainClient() if err != nil { return err } // Request notifications for transactions sending to all wallet // addresses. var ( addrs []btcutil.Address unspent []wtxmgr.Credit ) err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { var err error addrs, unspent, err = w.activeData(dbtx) return err }) if err != nil { return err } // TODO(jrick): How should this handle a synced height earlier than // the chain server best block? // When no addresses have been generated for the wallet, the rescan can // be skipped. // // TODO: This is only correct because activeData above returns all // addresses ever created, including those that don't need to be watched // anymore. This code should be updated when this assumption is no // longer true, but worst case would result in an unnecessary rescan. if len(addrs) == 0 && len(unspent) == 0 && w.Manager.SyncedTo().Height == 0 { // Find the latest checkpoint's height. This lets us catch up to // at least that checkpoint, since we're synchronizing from // scratch, and lets us avoid a bunch of costly DB transactions // in the case when we're using BDB for the walletdb backend and // Neutrino for the chain.Interface backend, and the chain // backend starts synchronizing at the same time as the wallet. _, bestHeight, err := chainClient.GetBestBlock() if err != nil { return err } checkHeight := bestHeight if len(w.chainParams.Checkpoints) > 0 { checkHeight = w.chainParams.Checkpoints[len( w.chainParams.Checkpoints)-1].Height } logHeight := checkHeight if bestHeight > logHeight { logHeight = bestHeight } log.Infof("Catching up block hashes to height %d, this will "+ "take a while...", logHeight) // Initialize the first database transaction. tx, err := w.db.BeginReadWriteTx() if err != nil { return err } ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) for height := int32(1); height <= bestHeight; height++ { hash, err := chainClient.GetBlockHash(int64(height)) if err != nil { tx.Rollback() return err } // If we've found the best height the backend knows // about, but we haven't reached the last checkpoint, we // know the backend is still synchronizing. We can give // it a little bit of time to synchronize further before // updating the best height based on the backend. Once // we see that the backend has advanced, we can catch // up to it. for height == bestHeight && bestHeight < checkHeight { time.Sleep(100 * time.Millisecond) _, bestHeight, err = chainClient.GetBestBlock() if err != nil { tx.Rollback() return err } // If we're using the Neutrino backend, we can // check if it's current or not. If it's not and // we've exceeded the original checkHeight, we // can keep the loop going by increasing the // checkHeight to be greater than the bestHeight // and if it is, we can set checkHeight to the // same as the bestHeight so the loop exits. switch c := chainClient.(type) { case *chain.NeutrinoClient: if c.CS.IsCurrent() { checkHeight = bestHeight } else if checkHeight < bestHeight+1 { checkHeight = bestHeight + 1 } } } err = w.Manager.SetSyncedTo(ns, &waddrmgr.BlockStamp{ Hash: *hash, Height: height, }) if err != nil { tx.Rollback() return err } // Every 10K blocks, commit and start a new database TX. if height%10000 == 0 { err = tx.Commit() if err != nil { tx.Rollback() return err } log.Infof("Caught up to height %d", height) tx, err = w.db.BeginReadWriteTx() if err != nil { return err } ns = tx.ReadWriteBucket(waddrmgrNamespaceKey) } } // Commit (or roll back) the final database transaction. err = tx.Commit() if err != nil { tx.Rollback() return err } log.Info("Done catching up block hashes") // Since we've spent some time catching up block hashes, we // might have new addresses waiting for us that were requested // during initial sync. Make sure we have those before we // request a rescan later on. err = walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { var err error addrs, unspent, err = w.activeData(dbtx) return err }) if err != nil { return err } } // Compare previously-seen blocks against the chain server. If any of // these blocks no longer exist, rollback all of the missing blocks // before catching up with the rescan. err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) rollback := false rollbackStamp := w.Manager.SyncedTo() for height := rollbackStamp.Height; true; height-- { hash, err := w.Manager.BlockHash(addrmgrNs, height) if err != nil { return err } chainHash, err := chainClient.GetBlockHash(int64(height)) if err != nil { return err } rollbackStamp.Hash = *chainHash rollbackStamp.Height = height if bytes.Equal(hash[:], chainHash[:]) { break } rollback = true } if rollback { err := w.Manager.SetSyncedTo(addrmgrNs, &rollbackStamp) if err != nil { return err } // Rollback unconfirms transactions at and beyond the passed // height, so add one to the new synced-to height to prevent // unconfirming txs from the synced-to block. err = w.TxStore.Rollback(txmgrNs, rollbackStamp.Height+1) if err != nil { return err } } return nil }) if err != nil { return err } // Request notifications for connected and disconnected blocks. // // TODO(jrick): Either request this notification only once, or when // rpcclient is modified to allow some notification request to not // automatically resent on reconnect, include the notifyblocks request // as well. I am leaning towards allowing off all rpcclient // notification re-registrations, in which case the code here should be // left as is. err = chainClient.NotifyBlocks() if err != nil { return err } return w.Rescan(addrs, unspent) } type ( createTxRequest struct { account uint32 outputs []*wire.TxOut minconf int32 feeSatPerKB btcutil.Amount resp chan createTxResponse } createTxResponse struct { tx *txauthor.AuthoredTx err error } ) // txCreator is responsible for the input selection and creation of // transactions. These functions are the responsibility of this method // (designed to be run as its own goroutine) since input selection must be // serialized, or else it is possible to create double spends by choosing the // same inputs for multiple transactions. Along with input selection, this // method is also responsible for the signing of transactions, since we don't // want to end up in a situation where we run out of inputs as multiple // transactions are being created. In this situation, it would then be possible // for both requests, rather than just one, to fail due to not enough available // inputs. func (w *Wallet) txCreator() { quit := w.quitChan() out: for { select { case txr := <-w.createTxRequests: heldUnlock, err := w.holdUnlock() if err != nil { txr.resp <- createTxResponse{nil, err} continue } tx, err := w.txToOutputs(txr.outputs, txr.account, txr.minconf, txr.feeSatPerKB) heldUnlock.release() txr.resp <- createTxResponse{tx, err} case <-quit: break out } } w.wg.Done() } // CreateSimpleTx creates a new signed transaction spending unspent P2PKH // outputs with at laest minconf confirmations spending to any number of // address/amount pairs. Change and an appropriate transaction fee are // automatically included, if necessary. All transaction creation through this // function is serialized to prevent the creation of many transactions which // spend the same outputs. func (w *Wallet) CreateSimpleTx(account uint32, outputs []*wire.TxOut, minconf int32, satPerKb btcutil.Amount) (*txauthor.AuthoredTx, error) { req := createTxRequest{ account: account, outputs: outputs, minconf: minconf, feeSatPerKB: satPerKb, resp: make(chan createTxResponse), } w.createTxRequests <- req resp := <-req.resp return resp.tx, resp.err } type ( unlockRequest struct { passphrase []byte lockAfter <-chan time.Time // nil prevents the timeout. err chan error } changePassphraseRequest struct { old, new []byte err chan error } // heldUnlock is a tool to prevent the wallet from automatically // locking after some timeout before an operation which needed // the unlocked wallet has finished. Any aquired heldUnlock // *must* be released (preferably with a defer) or the wallet // will forever remain unlocked. heldUnlock chan struct{} ) // walletLocker manages the locked/unlocked state of a wallet. func (w *Wallet) walletLocker() { var timeout <-chan time.Time holdChan := make(heldUnlock) quit := w.quitChan() out: for { select { case req := <-w.unlockRequests: err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) return w.Manager.Unlock(addrmgrNs, req.passphrase) }) if err != nil { req.err <- err continue } timeout = req.lockAfter if timeout == nil { log.Info("The wallet has been unlocked without a time limit") } else { log.Info("The wallet has been temporarily unlocked") } req.err <- nil continue case req := <-w.changePassphrase: err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) return w.Manager.ChangePassphrase(addrmgrNs, req.old, req.new, true, &waddrmgr.DefaultScryptOptions) }) req.err <- err continue case req := <-w.holdUnlockRequests: if w.Manager.IsLocked() { close(req) continue } req <- holdChan <-holdChan // Block until the lock is released. // If, after holding onto the unlocked wallet for some // time, the timeout has expired, lock it now instead // of hoping it gets unlocked next time the top level // select runs. select { case <-timeout: // Let the top level select fallthrough so the // wallet is locked. default: continue } case w.lockState <- w.Manager.IsLocked(): continue case <-quit: break out case <-w.lockRequests: case <-timeout: } // Select statement fell through by an explicit lock or the // timer expiring. Lock the manager here. timeout = nil err := w.Manager.Lock() if err != nil && !waddrmgr.IsError(err, waddrmgr.ErrLocked) { log.Errorf("Could not lock wallet: %v", err) } else { log.Info("The wallet has been locked") } } w.wg.Done() } // Unlock unlocks the wallet's address manager and relocks it after timeout has // expired. If the wallet is already unlocked and the new passphrase is // correct, the current timeout is replaced with the new one. The wallet will // be locked if the passphrase is incorrect or any other error occurs during the // unlock. func (w *Wallet) Unlock(passphrase []byte, lock <-chan time.Time) error { err := make(chan error, 1) w.unlockRequests <- unlockRequest{ passphrase: passphrase, lockAfter: lock, err: err, } return <-err } // Lock locks the wallet's address manager. func (w *Wallet) Lock() { w.lockRequests <- struct{}{} } // Locked returns whether the account manager for a wallet is locked. func (w *Wallet) Locked() bool { return <-w.lockState } // holdUnlock prevents the wallet from being locked. The heldUnlock object // *must* be released, or the wallet will forever remain unlocked. // // TODO: To prevent the above scenario, perhaps closures should be passed // to the walletLocker goroutine and disallow callers from explicitly // handling the locking mechanism. func (w *Wallet) holdUnlock() (heldUnlock, error) { req := make(chan heldUnlock) w.holdUnlockRequests <- req hl, ok := <-req if !ok { // TODO(davec): This should be defined and exported from // waddrmgr. return nil, waddrmgr.ManagerError{ ErrorCode: waddrmgr.ErrLocked, Description: "address manager is locked", } } return hl, nil } // release releases the hold on the unlocked-state of the wallet and allows the // wallet to be locked again. If a lock timeout has already expired, the // wallet is locked again as soon as release is called. func (c heldUnlock) release() { c <- struct{}{} } // ChangePrivatePassphrase attempts to change the passphrase for a wallet from // old to new. Changing the passphrase is synchronized with all other address // manager locking and unlocking. The lock state will be the same as it was // before the password change. func (w *Wallet) ChangePrivatePassphrase(old, new []byte) error { err := make(chan error, 1) w.changePassphrase <- changePassphraseRequest{ old: old, new: new, err: err, } return <-err } // ChangePublicPassphrase modifies the public passphrase of the wallet. func (w *Wallet) ChangePublicPassphrase(old, new []byte) error { return walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) return w.Manager.ChangePassphrase(addrmgrNs, old, new, false, &waddrmgr.DefaultScryptOptions) }) } // accountUsed returns whether there are any recorded transactions spending to // a given account. It returns true if atleast one address in the account was // used and false if no address in the account was used. func (w *Wallet) accountUsed(addrmgrNs walletdb.ReadWriteBucket, account uint32) (bool, error) { var used bool err := w.Manager.ForEachAccountAddress(addrmgrNs, account, func(maddr waddrmgr.ManagedAddress) error { used = maddr.Used(addrmgrNs) if used { return waddrmgr.Break } return nil }) if err == waddrmgr.Break { err = nil } return used, err } // AccountAddresses returns the addresses for every created address for an // account. func (w *Wallet) AccountAddresses(account uint32) (addrs []btcutil.Address, err error) { err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) return w.Manager.ForEachAccountAddress(addrmgrNs, account, func(maddr waddrmgr.ManagedAddress) error { addrs = append(addrs, maddr.Address()) return nil }) }) return } // CalculateBalance sums the amounts of all unspent transaction // outputs to addresses of a wallet and returns the balance. // // If confirmations is 0, all UTXOs, even those not present in a // block (height -1), will be used to get the balance. Otherwise, // a UTXO must be in a block. If confirmations is 1 or greater, // the balance will be calculated based on how many how many blocks // include a UTXO. func (w *Wallet) CalculateBalance(confirms int32) (btcutil.Amount, error) { var balance btcutil.Amount err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error blk := w.Manager.SyncedTo() balance, err = w.TxStore.Balance(txmgrNs, confirms, blk.Height) return err }) return balance, err } // Balances records total, spendable (by policy), and immature coinbase // reward balance amounts. type Balances struct { Total btcutil.Amount Spendable btcutil.Amount ImmatureReward btcutil.Amount } // CalculateAccountBalances sums the amounts of all unspent transaction // outputs to the given account of a wallet and returns the balance. // // This function is much slower than it needs to be since transactions outputs // are not indexed by the accounts they credit to, and all unspent transaction // outputs must be iterated. func (w *Wallet) CalculateAccountBalances(account uint32, confirms int32) (Balances, error) { var bals Balances err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } for i := range unspent { output := &unspent[i] var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err != nil || outputAcct != account { continue } bals.Total += output.Amount if output.FromCoinBase && !confirmed(int32(w.chainParams.CoinbaseMaturity), output.Height, syncBlock.Height) { bals.ImmatureReward += output.Amount } else if confirmed(confirms, output.Height, syncBlock.Height) { bals.Spendable += output.Amount } } return nil }) return bals, err } // CurrentAddress gets the most recently requested Bitcoin payment address // from a wallet. If the address has already been used (there is at least // one transaction spending to it in the blockchain or btcd mempool), the next // chained address is returned. func (w *Wallet) CurrentAddress(account uint32) (btcutil.Address, error) { var addr btcutil.Address err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) maddr, err := w.Manager.LastExternalAddress(addrmgrNs, account) if err != nil { // If no address exists yet, create the first external address if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { addr, err = w.newAddress(addrmgrNs, account) } return err } // Get next chained address if the last one has already been used. if maddr.Used(addrmgrNs) { addr, err = w.newAddress(addrmgrNs, account, waddrmgr.WitnessPubKey) return err } addr = maddr.Address() return nil }) return addr, err } // PubKeyForAddress looks up the associated public key for a P2PKH address. func (w *Wallet) PubKeyForAddress(a btcutil.Address) (*btcec.PublicKey, error) { var pubKey *btcec.PublicKey err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) managedAddr, err := w.Manager.Address(addrmgrNs, a) if err != nil { return err } managedPubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress) if !ok { return errors.New("address does not have an associated public key") } pubKey = managedPubKeyAddr.PubKey() return nil }) return pubKey, err } // PrivKeyForAddress looks up the associated private key for a P2PKH or P2PK // address. func (w *Wallet) PrivKeyForAddress(a btcutil.Address) (*btcec.PrivateKey, error) { var privKey *btcec.PrivateKey err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) managedAddr, err := w.Manager.Address(addrmgrNs, a) if err != nil { return err } managedPubKeyAddr, ok := managedAddr.(waddrmgr.ManagedPubKeyAddress) if !ok { return errors.New("address does not have an associated private key") } privKey, err = managedPubKeyAddr.PrivKey() return err }) return privKey, err } // HaveAddress returns whether the wallet is the owner of the address a. func (w *Wallet) HaveAddress(a btcutil.Address) (bool, error) { err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) _, err := w.Manager.Address(addrmgrNs, a) return err }) if err == nil { return true, nil } if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { return false, nil } return false, err } // AccountOfAddress finds the account that an address is associated with. func (w *Wallet) AccountOfAddress(a btcutil.Address) (uint32, error) { var account uint32 err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error account, err = w.Manager.AddrAccount(addrmgrNs, a) return err }) return account, err } // AddressInfo returns detailed information regarding a wallet address. func (w *Wallet) AddressInfo(a btcutil.Address) (waddrmgr.ManagedAddress, error) { var managedAddress waddrmgr.ManagedAddress err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error managedAddress, err = w.Manager.Address(addrmgrNs, a) return err }) return managedAddress, err } // AccountNumber returns the account number for an account name. func (w *Wallet) AccountNumber(accountName string) (uint32, error) { var account uint32 err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error account, err = w.Manager.LookupAccount(addrmgrNs, accountName) return err }) return account, err } // AccountName returns the name of an account. func (w *Wallet) AccountName(accountNumber uint32) (string, error) { var accountName string err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error accountName, err = w.Manager.AccountName(addrmgrNs, accountNumber) return err }) return accountName, err } // AccountProperties returns the properties of an account, including address // indexes and name. It first fetches the desynced information from the address // manager, then updates the indexes based on the address pools. func (w *Wallet) AccountProperties(acct uint32) (*waddrmgr.AccountProperties, error) { var props *waddrmgr.AccountProperties err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var err error props, err = w.Manager.AccountProperties(waddrmgrNs, acct) return err }) return props, err } // RenameAccount sets the name for an account number to newName. func (w *Wallet) RenameAccount(account uint32, newName string) error { var props *waddrmgr.AccountProperties err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) err := w.Manager.RenameAccount(addrmgrNs, account, newName) if err != nil { return err } props, err = w.Manager.AccountProperties(addrmgrNs, account) return err }) if err == nil { w.NtfnServer.notifyAccountProperties(props) } return err } const maxEmptyAccounts = 100 // NextAccount creates the next account and returns its account number. The // name must be unique to the account. In order to support automatic seed // restoring, new accounts may not be created when all of the previous 100 // accounts have no transaction history (this is a deviation from the BIP0044 // spec, which allows no unused account gaps). func (w *Wallet) NextAccount(name string) (uint32, error) { var account uint32 var props *waddrmgr.AccountProperties err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) var err error account, err = w.Manager.NewAccount(addrmgrNs, name) if err != nil { return err } props, err = w.Manager.AccountProperties(addrmgrNs, account) return err }) if err != nil { log.Errorf("Cannot fetch new account properties for notification "+ "after account creation: %v", err) } else { w.NtfnServer.notifyAccountProperties(props) } return account, err } // CreditCategory describes the type of wallet transaction output. The category // of "sent transactions" (debits) is always "send", and is not expressed by // this type. // // TODO: This is a requirement of the RPC server and should be moved. type CreditCategory byte // These constants define the possible credit categories. const ( CreditReceive CreditCategory = iota CreditGenerate CreditImmature ) // String returns the category as a string. This string may be used as the // JSON string for categories as part of listtransactions and gettransaction // RPC responses. func (c CreditCategory) String() string { switch c { case CreditReceive: return "receive" case CreditGenerate: return "generate" case CreditImmature: return "immature" default: return "unknown" } } // RecvCategory returns the category of received credit outputs from a // transaction record. The passed block chain height is used to distinguish // immature from mature coinbase outputs. // // TODO: This is intended for use by the RPC server and should be moved out of // this package at a later time. func RecvCategory(details *wtxmgr.TxDetails, syncHeight int32, net *chaincfg.Params) CreditCategory { if blockchain.IsCoinBaseTx(&details.MsgTx) { if confirmed(int32(net.CoinbaseMaturity), details.Block.Height, syncHeight) { return CreditGenerate } return CreditImmature } return CreditReceive } // listTransactions creates a object that may be marshalled to a response result // for a listtransactions RPC. // // TODO: This should be moved to the legacyrpc package. func listTransactions(tx walletdb.ReadTx, details *wtxmgr.TxDetails, addrMgr *waddrmgr.Manager, syncHeight int32, net *chaincfg.Params) []btcjson.ListTransactionsResult { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) var ( blockHashStr string blockTime int64 confirmations int64 ) if details.Block.Height != -1 { blockHashStr = details.Block.Hash.String() blockTime = details.Block.Time.Unix() confirmations = int64(confirms(details.Block.Height, syncHeight)) } results := []btcjson.ListTransactionsResult{} txHashStr := details.Hash.String() received := details.Received.Unix() generated := blockchain.IsCoinBaseTx(&details.MsgTx) recvCat := RecvCategory(details, syncHeight, net).String() send := len(details.Debits) != 0 // Fee can only be determined if every input is a debit. var feeF64 float64 if len(details.Debits) == len(details.MsgTx.TxIn) { var debitTotal btcutil.Amount for _, deb := range details.Debits { debitTotal += deb.Amount } var outputTotal btcutil.Amount for _, output := range details.MsgTx.TxOut { outputTotal += btcutil.Amount(output.Value) } // Note: The actual fee is debitTotal - outputTotal. However, // this RPC reports negative numbers for fees, so the inverse // is calculated. feeF64 = (outputTotal - debitTotal).ToBTC() } outputs: for i, output := range details.MsgTx.TxOut { // Determine if this output is a credit, and if so, determine // its spentness. var isCredit bool var spentCredit bool for _, cred := range details.Credits { if cred.Index == uint32(i) { // Change outputs are ignored. if cred.Change { continue outputs } isCredit = true spentCredit = cred.Spent break } } var address string var accountName string _, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript, net) if len(addrs) == 1 { addr := addrs[0] address = addr.EncodeAddress() account, err := addrMgr.AddrAccount(addrmgrNs, addrs[0]) if err == nil { accountName, err = addrMgr.AccountName(addrmgrNs, account) if err != nil { accountName = "" } } } amountF64 := btcutil.Amount(output.Value).ToBTC() result := btcjson.ListTransactionsResult{ // Fields left zeroed: // InvolvesWatchOnly // BlockIndex // // Fields set below: // Account (only for non-"send" categories) // Category // Amount // Fee Address: address, Vout: uint32(i), Confirmations: confirmations, Generated: generated, BlockHash: blockHashStr, BlockTime: blockTime, TxID: txHashStr, WalletConflicts: []string{}, Time: received, TimeReceived: received, } // Add a received/generated/immature result if this is a credit. // If the output was spent, create a second result under the // send category with the inverse of the output amount. It is // therefore possible that a single output may be included in // the results set zero, one, or two times. // // Since credits are not saved for outputs that are not // controlled by this wallet, all non-credits from transactions // with debits are grouped under the send category. if send || spentCredit { result.Category = "send" result.Amount = -amountF64 result.Fee = &feeF64 results = append(results, result) } if isCredit { result.Account = accountName result.Category = recvCat result.Amount = amountF64 result.Fee = nil results = append(results, result) } } return results } // ListSinceBlock returns a slice of objects with details about transactions // since the given block. If the block is -1 then all transactions are included. // This is intended to be used for listsinceblock RPC replies. func (w *Wallet) ListSinceBlock(start, end, syncHeight int32) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { for _, detail := range details { jsonResults := listTransactions(tx, &detail, w.Manager, syncHeight, w.chainParams) txList = append(txList, jsonResults...) } return false, nil } return w.TxStore.RangeTransactions(txmgrNs, start, end, rangeFn) }) return txList, err } // ListTransactions returns a slice of objects with details about a recorded // transaction. This is intended to be used for listtransactions RPC // replies. func (w *Wallet) ListTransactions(from, count int) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() // Need to skip the first from transactions, and after those, only // include the next count transactions. skipped := 0 n := 0 rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { // Iterate over transactions at this height in reverse order. // This does nothing for unmined transactions, which are // unsorted, but it will process mined transactions in the // reverse order they were marked mined. for i := len(details) - 1; i >= 0; i-- { if from > skipped { skipped++ continue } n++ if n > count { return true, nil } jsonResults := listTransactions(tx, &details[i], w.Manager, syncBlock.Height, w.chainParams) txList = append(txList, jsonResults...) if len(jsonResults) > 0 { n++ } } return false, nil } // Return newer results first by starting at mempool height and working // down to the genesis block. return w.TxStore.RangeTransactions(txmgrNs, -1, 0, rangeFn) }) return txList, err } // ListAddressTransactions returns a slice of objects with details about // recorded transactions to or from any address belonging to a set. This is // intended to be used for listaddresstransactions RPC replies. func (w *Wallet) ListAddressTransactions(pkHashes map[string]struct{}) ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { loopDetails: for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs( pkScript, w.chainParams) if err != nil || len(addrs) != 1 { continue } apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash) if !ok { continue } _, ok = pkHashes[string(apkh.ScriptAddress())] if !ok { continue } jsonResults := listTransactions(tx, detail, w.Manager, syncBlock.Height, w.chainParams) if err != nil { return false, err } txList = append(txList, jsonResults...) continue loopDetails } } return false, nil } return w.TxStore.RangeTransactions(txmgrNs, 0, -1, rangeFn) }) return txList, err } // ListAllTransactions returns a slice of objects with details about a recorded // transaction. This is intended to be used for listalltransactions RPC // replies. func (w *Wallet) ListAllTransactions() ([]btcjson.ListTransactionsResult, error) { txList := []btcjson.ListTransactionsResult{} err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) // Get current block. The block height used for calculating // the number of tx confirmations. syncBlock := w.Manager.SyncedTo() rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { // Iterate over transactions at this height in reverse order. // This does nothing for unmined transactions, which are // unsorted, but it will process mined transactions in the // reverse order they were marked mined. for i := len(details) - 1; i >= 0; i-- { jsonResults := listTransactions(tx, &details[i], w.Manager, syncBlock.Height, w.chainParams) txList = append(txList, jsonResults...) } return false, nil } // Return newer results first by starting at mempool height and // working down to the genesis block. return w.TxStore.RangeTransactions(txmgrNs, -1, 0, rangeFn) }) return txList, err } // BlockIdentifier identifies a block by either a height or a hash. type BlockIdentifier struct { height int32 hash *chainhash.Hash } // NewBlockIdentifierFromHeight constructs a BlockIdentifier for a block height. func NewBlockIdentifierFromHeight(height int32) *BlockIdentifier { return &BlockIdentifier{height: height} } // NewBlockIdentifierFromHash constructs a BlockIdentifier for a block hash. func NewBlockIdentifierFromHash(hash *chainhash.Hash) *BlockIdentifier { return &BlockIdentifier{hash: hash} } // GetTransactionsResult is the result of the wallet's GetTransactions method. // See GetTransactions for more details. type GetTransactionsResult struct { MinedTransactions []Block UnminedTransactions []TransactionSummary } // GetTransactions returns transaction results between a starting and ending // block. Blocks in the block range may be specified by either a height or a // hash. // // Because this is a possibly lenghtly operation, a cancel channel is provided // to cancel the task. If this channel unblocks, the results created thus far // will be returned. // // Transaction results are organized by blocks in ascending order and unmined // transactions in an unspecified order. Mined transactions are saved in a // Block structure which records properties about the block. func (w *Wallet) GetTransactions(startBlock, endBlock *BlockIdentifier, cancel <-chan struct{}) (*GetTransactionsResult, error) { var start, end int32 = 0, -1 w.chainClientLock.Lock() chainClient := w.chainClient w.chainClientLock.Unlock() // TODO: Fetching block heights by their hashes is inherently racy // because not all block headers are saved but when they are for SPV the // db can be queried directly without this. var startResp, endResp rpcclient.FutureGetBlockVerboseResult if startBlock != nil { if startBlock.hash == nil { start = startBlock.height } else { if chainClient == nil { return nil, errors.New("no chain server client") } switch client := chainClient.(type) { case *chain.RPCClient: startResp = client.GetBlockVerboseTxAsync(startBlock.hash) case *chain.BitcoindClient: var err error start, err = client.GetBlockHeight(startBlock.hash) if err != nil { return nil, err } case *chain.NeutrinoClient: var err error start, err = client.GetBlockHeight(startBlock.hash) if err != nil { return nil, err } } } } if endBlock != nil { if endBlock.hash == nil { end = endBlock.height } else { if chainClient == nil { return nil, errors.New("no chain server client") } switch client := chainClient.(type) { case *chain.RPCClient: endResp = client.GetBlockVerboseTxAsync(endBlock.hash) case *chain.NeutrinoClient: var err error end, err = client.GetBlockHeight(endBlock.hash) if err != nil { return nil, err } } } } if startResp != nil { resp, err := startResp.Receive() if err != nil { return nil, err } start = int32(resp.Height) } if endResp != nil { resp, err := endResp.Receive() if err != nil { return nil, err } end = int32(resp.Height) } var res GetTransactionsResult err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { // TODO: probably should make RangeTransactions not reuse the // details backing array memory. dets := make([]wtxmgr.TxDetails, len(details)) copy(dets, details) details = dets txs := make([]TransactionSummary, 0, len(details)) for i := range details { txs = append(txs, makeTxSummary(dbtx, w, &details[i])) } if details[0].Block.Height != -1 { blockHash := details[0].Block.Hash res.MinedTransactions = append(res.MinedTransactions, Block{ Hash: &blockHash, Height: details[0].Block.Height, Timestamp: details[0].Block.Time.Unix(), Transactions: txs, }) } else { res.UnminedTransactions = txs } select { case <-cancel: return true, nil default: return false, nil } } return w.TxStore.RangeTransactions(txmgrNs, start, end, rangeFn) }) return &res, err } // AccountResult is a single account result for the AccountsResult type. type AccountResult struct { waddrmgr.AccountProperties TotalBalance btcutil.Amount } // AccountsResult is the resutl of the wallet's Accounts method. See that // method for more details. type AccountsResult struct { Accounts []AccountResult CurrentBlockHash *chainhash.Hash CurrentBlockHeight int32 } // Accounts returns the current names, numbers, and total balances of all // accounts in the wallet. The current chain tip is included in the result for // atomicity reasons. // // TODO(jrick): Is the chain tip really needed, since only the total balances // are included? func (w *Wallet) Accounts() (*AccountsResult, error) { var ( accounts []AccountResult syncBlockHash *chainhash.Hash syncBlockHeight int32 ) err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) syncBlock := w.Manager.SyncedTo() syncBlockHash = &syncBlock.Hash syncBlockHeight = syncBlock.Height unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } err = w.Manager.ForEachAccount(addrmgrNs, func(acct uint32) error { props, err := w.Manager.AccountProperties(addrmgrNs, acct) if err != nil { return err } accounts = append(accounts, AccountResult{ AccountProperties: *props, // TotalBalance set below }) return nil }) if err != nil { return err } m := make(map[uint32]*btcutil.Amount) for i := range accounts { a := &accounts[i] m[a.AccountNumber] = &a.TotalBalance } for i := range unspent { output := unspent[i] var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err == nil { amt, ok := m[outputAcct] if ok { *amt += output.Amount } } } return nil }) return &AccountsResult{ Accounts: accounts, CurrentBlockHash: syncBlockHash, CurrentBlockHeight: syncBlockHeight, }, err } // AccountBalanceResult is a single result for the Wallet.AccountBalances method. type AccountBalanceResult struct { AccountNumber uint32 AccountName string AccountBalance btcutil.Amount } // AccountBalances returns all accounts in the wallet and their balances. // Balances are determined by excluding transactions that have not met // requiredConfs confirmations. func (w *Wallet) AccountBalances(requiredConfs int32) ([]AccountBalanceResult, error) { var results []AccountBalanceResult err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) syncBlock := w.Manager.SyncedTo() // Fill out all account info except for the balances. lastAcct, err := w.Manager.LastAccount(addrmgrNs) if err != nil { return err } results = make([]AccountBalanceResult, lastAcct+2) for i := range results[:len(results)-1] { accountName, err := w.Manager.AccountName(addrmgrNs, uint32(i)) if err != nil { return err } results[i].AccountNumber = uint32(i) results[i].AccountName = accountName } results[len(results)-1].AccountNumber = waddrmgr.ImportedAddrAccount results[len(results)-1].AccountName = waddrmgr.ImportedAddrAccountName // Fetch all unspent outputs, and iterate over them tallying each // account's balance where the output script pays to an account address // and the required number of confirmations is met. unspentOutputs, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } for i := range unspentOutputs { output := &unspentOutputs[i] if !confirmed(requiredConfs, output.Height, syncBlock.Height) { continue } if output.FromCoinBase && !confirmed(int32(w.ChainParams().CoinbaseMaturity), output.Height, syncBlock.Height) { continue } _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript, w.chainParams) if err != nil || len(addrs) == 0 { continue } outputAcct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err != nil { continue } switch { case outputAcct == waddrmgr.ImportedAddrAccount: results[len(results)-1].AccountBalance += output.Amount case outputAcct > lastAcct: return errors.New("waddrmgr.Manager.AddrAccount returned account " + "beyond recorded last account") default: results[outputAcct].AccountBalance += output.Amount } } return nil }) return results, err } // creditSlice satisifies the sort.Interface interface to provide sorting // transaction credits from oldest to newest. Credits with the same receive // time and mined in the same block are not guaranteed to be sorted by the order // they appear in the block. Credits from the same transaction are sorted by // output index. type creditSlice []wtxmgr.Credit func (s creditSlice) Len() int { return len(s) } func (s creditSlice) Less(i, j int) bool { switch { // If both credits are from the same tx, sort by output index. case s[i].OutPoint.Hash == s[j].OutPoint.Hash: return s[i].OutPoint.Index < s[j].OutPoint.Index // If both transactions are unmined, sort by their received date. case s[i].Height == -1 && s[j].Height == -1: return s[i].Received.Before(s[j].Received) // Unmined (newer) txs always come last. case s[i].Height == -1: return false case s[j].Height == -1: return true // If both txs are mined in different blocks, sort by block height. default: return s[i].Height < s[j].Height } } func (s creditSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } // ListUnspent returns a slice of objects representing the unspent wallet // transactions fitting the given criteria. The confirmations will be more than // minconf, less than maxconf and if addresses is populated only the addresses // contained within it will be considered. If we know nothing about a // transaction an empty array will be returned. func (w *Wallet) ListUnspent(minconf, maxconf int32, addresses map[string]struct{}) ([]*btcjson.ListUnspentResult, error) { var results []*btcjson.ListUnspentResult err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) syncBlock := w.Manager.SyncedTo() filter := len(addresses) != 0 unspent, err := w.TxStore.UnspentOutputs(txmgrNs) if err != nil { return err } sort.Sort(sort.Reverse(creditSlice(unspent))) defaultAccountName, err := w.Manager.AccountName(addrmgrNs, waddrmgr.DefaultAccountNum) if err != nil { return err } results = make([]*btcjson.ListUnspentResult, 0, len(unspent)) for i := range unspent { output := unspent[i] // Outputs with fewer confirmations than the minimum or more // confs than the maximum are excluded. confs := confirms(output.Height, syncBlock.Height) if confs < minconf || confs > maxconf { continue } // Only mature coinbase outputs are included. if output.FromCoinBase { target := int32(w.ChainParams().CoinbaseMaturity) if !confirmed(target, output.Height, syncBlock.Height) { continue } } // Exclude locked outputs from the result set. if w.LockedOutpoint(output.OutPoint) { continue } // Lookup the associated account for the output. Use the // default account name in case there is no associated account // for some reason, although this should never happen. // // This will be unnecessary once transactions and outputs are // grouped under the associated account in the db. acctName := defaultAccountName sc, addrs, _, err := txscript.ExtractPkScriptAddrs( output.PkScript, w.chainParams) if err != nil { continue } if len(addrs) > 0 { acct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) if err == nil { s, err := w.Manager.AccountName(addrmgrNs, acct) if err == nil { acctName = s } } } if filter { for _, addr := range addrs { _, ok := addresses[addr.EncodeAddress()] if ok { goto include } } continue } include: // At the moment watch-only addresses are not supported, so all // recorded outputs that are not multisig are "spendable". // Multisig outputs are only "spendable" if all keys are // controlled by this wallet. // // TODO: Each case will need updates when watch-only addrs // is added. For P2PK, P2PKH, and P2SH, the address must be // looked up and not be watching-only. For multisig, all // pubkeys must belong to the manager with the associated // private key (currently it only checks whether the pubkey // exists, since the private key is required at the moment). var spendable bool scSwitch: switch sc { case txscript.PubKeyHashTy: spendable = true case txscript.PubKeyTy: spendable = true case txscript.WitnessV0ScriptHashTy: spendable = true case txscript.WitnessV0PubKeyHashTy: spendable = true case txscript.MultiSigTy: for _, a := range addrs { _, err := w.Manager.Address(addrmgrNs, a) if err == nil { continue } if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { break scSwitch } return err } spendable = true } result := &btcjson.ListUnspentResult{ TxID: output.OutPoint.Hash.String(), Vout: output.OutPoint.Index, Account: acctName, ScriptPubKey: hex.EncodeToString(output.PkScript), Amount: output.Amount.ToBTC(), Confirmations: int64(confs), Spendable: spendable, } // BUG: this should be a JSON array so that all // addresses can be included, or removed (and the // caller extracts addresses from the pkScript). if len(addrs) > 0 { result.Address = addrs[0].EncodeAddress() } results = append(results, result) } return nil }) return results, err } // DumpPrivKeys returns the WIF-encoded private keys for all addresses with // private keys in a wallet. func (w *Wallet) DumpPrivKeys() ([]string, error) { var privkeys []string err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) // Iterate over each active address, appending the private key to // privkeys. return w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { ma, err := w.Manager.Address(addrmgrNs, addr) if err != nil { return err } // Only those addresses with keys needed. pka, ok := ma.(waddrmgr.ManagedPubKeyAddress) if !ok { return nil } wif, err := pka.ExportPrivKey() if err != nil { // It would be nice to zero out the array here. However, // since strings in go are immutable, and we have no // control over the caller I don't think we can. :( return err } privkeys = append(privkeys, wif.String()) return nil }) }) return privkeys, err } // DumpWIFPrivateKey returns the WIF encoded private key for a // single wallet address. func (w *Wallet) DumpWIFPrivateKey(addr btcutil.Address) (string, error) { var maddr waddrmgr.ManagedAddress err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { waddrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) // Get private key from wallet if it exists. var err error maddr, err = w.Manager.Address(waddrmgrNs, addr) return err }) if err != nil { return "", err } pka, ok := maddr.(waddrmgr.ManagedPubKeyAddress) if !ok { return "", fmt.Errorf("address %s is not a key type", addr) } wif, err := pka.ExportPrivKey() if err != nil { return "", err } return wif.String(), nil } // ImportPrivateKey imports a private key to the wallet and writes the new // wallet to disk. func (w *Wallet) ImportPrivateKey(wif *btcutil.WIF, bs *waddrmgr.BlockStamp, rescan bool) (string, error) { // The starting block for the key is the genesis block unless otherwise // specified. var newBirthday time.Time if bs == nil { bs = &waddrmgr.BlockStamp{ Hash: *w.chainParams.GenesisHash, Height: 0, } } else { header, err := w.chainClient.GetBlockHeader(&bs.Hash) // Only update the new birthday time from default value if we // actually have timestamp info in the header. if err == nil { newBirthday = header.Timestamp } } // Attempt to import private key into wallet. var addr btcutil.Address var props *waddrmgr.AccountProperties err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) maddr, err := w.Manager.ImportPrivateKey(addrmgrNs, wif, bs) if err != nil { return err } addr = maddr.Address() props, err = w.Manager.AccountProperties( addrmgrNs, waddrmgr.ImportedAddrAccount) if err != nil { return err } return w.Manager.SetBirthday(addrmgrNs, newBirthday) }) if err != nil { return "", err } // Rescan blockchain for transactions with txout scripts paying to the // imported address. if rescan { job := &RescanJob{ Addrs: []btcutil.Address{addr}, OutPoints: nil, BlockStamp: *bs, } // Submit rescan job and log when the import has completed. // Do not block on finishing the rescan. The rescan success // or failure is logged elsewhere, and the channel is not // required to be read, so discard the return value. _ = w.SubmitRescan(job) } else { err := w.chainClient.NotifyReceived([]btcutil.Address{addr}) if err != nil { return "", fmt.Errorf("Failed to subscribe for address ntfns for "+ "address %s: %s", addr.EncodeAddress(), err) } } addrStr := addr.EncodeAddress() log.Infof("Imported payment address %s", addrStr) w.NtfnServer.notifyAccountProperties(props) // Return the payment address string of the imported private key. return addrStr, nil } // LockedOutpoint returns whether an outpoint has been marked as locked and // should not be used as an input for created transactions. func (w *Wallet) LockedOutpoint(op wire.OutPoint) bool { _, locked := w.lockedOutpoints[op] return locked } // LockOutpoint marks an outpoint as locked, that is, it should not be used as // an input for newly created transactions. func (w *Wallet) LockOutpoint(op wire.OutPoint) { w.lockedOutpoints[op] = struct{}{} } // UnlockOutpoint marks an outpoint as unlocked, that is, it may be used as an // input for newly created transactions. func (w *Wallet) UnlockOutpoint(op wire.OutPoint) { delete(w.lockedOutpoints, op) } // ResetLockedOutpoints resets the set of locked outpoints so all may be used // as inputs for new transactions. func (w *Wallet) ResetLockedOutpoints() { w.lockedOutpoints = map[wire.OutPoint]struct{}{} } // LockedOutpoints returns a slice of currently locked outpoints. This is // intended to be used by marshaling the result as a JSON array for // listlockunspent RPC results. func (w *Wallet) LockedOutpoints() []btcjson.TransactionInput { locked := make([]btcjson.TransactionInput, len(w.lockedOutpoints)) i := 0 for op := range w.lockedOutpoints { locked[i] = btcjson.TransactionInput{ Txid: op.Hash.String(), Vout: op.Index, } i++ } return locked } // resendUnminedTxs iterates through all transactions that spend from wallet // credits that are not known to have been mined into a block, and attempts // to send each to the chain server for relay. func (w *Wallet) resendUnminedTxs() { chainClient, err := w.requireChainClient() if err != nil { log.Errorf("No chain server available to resend unmined transactions") return } var txs []*wire.MsgTx err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error txs, err = w.TxStore.UnminedTxs(txmgrNs) return err }) if err != nil { log.Errorf("Cannot load unmined transactions for resending: %v", err) return } for _, tx := range txs { resp, err := chainClient.SendRawTransaction(tx, false) if err != nil { // TODO(jrick): Check error for if this tx is a double spend, // remove it if so. log.Debugf("Could not resend transaction %v: %v", tx.TxHash(), err) continue } log.Debugf("Resent unmined transaction %v", resp) } } // SortedActivePaymentAddresses returns a slice of all active payment // addresses in a wallet. func (w *Wallet) SortedActivePaymentAddresses() ([]string, error) { var addrStrs []string err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) return w.Manager.ForEachActiveAddress(addrmgrNs, func(addr btcutil.Address) error { addrStrs = append(addrStrs, addr.EncodeAddress()) return nil }) }) if err != nil { return nil, err } sort.Sort(sort.StringSlice(addrStrs)) return addrStrs, nil } // NewAddress returns the next external chained address for a wallet. func (w *Wallet) NewAddress(account uint32) (addr btcutil.Address, err error) { err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) var err error addr, err = w.newAddress(addrmgrNs, account) return err }) return } func (w *Wallet) newAddress(addrmgrNs walletdb.ReadWriteBucket, account uint32) (btcutil.Address, error) { // Get next address from wallet. addrs, err := w.Manager.NextExternalAddresses(addrmgrNs, account, 1) if err != nil { return nil, err } // Request updates from btcd for new transactions sent to this address. utilAddrs := make([]btcutil.Address, len(addrs)) for i, addr := range addrs { utilAddrs[i] = addr.Address() } w.chainClientLock.Lock() chainClient := w.chainClient w.chainClientLock.Unlock() if chainClient != nil { err := chainClient.NotifyReceived(utilAddrs) if err != nil { return nil, err } } props, err := w.Manager.AccountProperties(addrmgrNs, account) if err != nil { log.Errorf("Cannot fetch account properties for notification "+ "after deriving next external address: %v", err) } else { w.NtfnServer.notifyAccountProperties(props) } return utilAddrs[0], nil } // NewChangeAddress returns a new change address for a wallet. func (w *Wallet) NewChangeAddress(account uint32) (addr btcutil.Address, err error) { err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { addrmgrNs := tx.ReadWriteBucket(waddrmgrNamespaceKey) var err error addr, err = w.newChangeAddress(addrmgrNs, account) return err }) return } func (w *Wallet) newChangeAddress(addrmgrNs walletdb.ReadWriteBucket, account uint32) (btcutil.Address, error) { // Get next chained change address from wallet for account. addrs, err := w.Manager.NextInternalAddresses(addrmgrNs, account, 1) if err != nil { return nil, err } // Request updates from btcd for new transactions sent to this address. utilAddrs := make([]btcutil.Address, len(addrs)) for i, addr := range addrs { utilAddrs[i] = addr.Address() } chainClient, err := w.requireChainClient() if err == nil { err = chainClient.NotifyReceived(utilAddrs) if err != nil { return nil, err } } return utilAddrs[0], nil } // confirmed checks whether a transaction at height txHeight has met minconf // confirmations for a blockchain at height curHeight. func confirmed(minconf, txHeight, curHeight int32) bool { return confirms(txHeight, curHeight) >= minconf } // confirms returns the number of confirmations for a transaction in a block at // height txHeight (or -1 for an unconfirmed tx) given the chain height // curHeight. func confirms(txHeight, curHeight int32) int32 { switch { case txHeight == -1, txHeight > curHeight: return 0 default: return curHeight - txHeight + 1 } } // AccountTotalReceivedResult is a single result for the // Wallet.TotalReceivedForAccounts method. type AccountTotalReceivedResult struct { AccountNumber uint32 AccountName string TotalReceived btcutil.Amount LastConfirmation int32 } // TotalReceivedForAccounts iterates through a wallet's transaction history, // returning the total amount of decred received for all accounts. func (w *Wallet) TotalReceivedForAccounts(minConf int32) ([]AccountTotalReceivedResult, error) { var results []AccountTotalReceivedResult err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) syncBlock := w.Manager.SyncedTo() err := w.Manager.ForEachAccount(addrmgrNs, func(account uint32) error { accountName, err := w.Manager.AccountName(addrmgrNs, account) if err != nil { return err } results = append(results, AccountTotalReceivedResult{ AccountNumber: account, AccountName: accountName, }) return nil }) if err != nil { return err } var stopHeight int32 if minConf > 0 { stopHeight = syncBlock.Height - minConf + 1 } else { stopHeight = -1 } rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript var outputAcct uint32 _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, w.chainParams) if err == nil && len(addrs) > 0 { outputAcct, err = w.Manager.AddrAccount(addrmgrNs, addrs[0]) } if err == nil { acctIndex := int(outputAcct) if outputAcct == waddrmgr.ImportedAddrAccount { acctIndex = len(results) - 1 } res := &results[acctIndex] res.TotalReceived += cred.Amount res.LastConfirmation = confirms( detail.Block.Height, syncBlock.Height) } } } return false, nil } return w.TxStore.RangeTransactions(txmgrNs, 0, stopHeight, rangeFn) }) return results, err } // TotalReceivedForAddr iterates through a wallet's transaction history, // returning the total amount of bitcoins received for a single wallet // address. func (w *Wallet) TotalReceivedForAddr(addr btcutil.Address, minConf int32) (btcutil.Amount, error) { var amount btcutil.Amount err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) syncBlock := w.Manager.SyncedTo() var ( addrStr = addr.EncodeAddress() stopHeight int32 ) if minConf > 0 { stopHeight = syncBlock.Height - minConf + 1 } else { stopHeight = -1 } rangeFn := func(details []wtxmgr.TxDetails) (bool, error) { for i := range details { detail := &details[i] for _, cred := range detail.Credits { pkScript := detail.MsgTx.TxOut[cred.Index].PkScript _, addrs, _, err := txscript.ExtractPkScriptAddrs(pkScript, w.chainParams) // An error creating addresses from the output script only // indicates a non-standard script, so ignore this credit. if err != nil { continue } for _, a := range addrs { if addrStr == a.EncodeAddress() { amount += cred.Amount break } } } } return false, nil } return w.TxStore.RangeTransactions(txmgrNs, 0, stopHeight, rangeFn) }) return amount, err } // SendOutputs creates and sends payment transactions. It returns the // transaction hash upon success. func (w *Wallet) SendOutputs(outputs []*wire.TxOut, account uint32, minconf int32, satPerKb btcutil.Amount) (*chainhash.Hash, error) { chainClient, err := w.requireChainClient() if err != nil { return nil, err } for _, output := range outputs { err = txrules.CheckOutput(output, satPerKb) if err != nil { return nil, err } } // Create transaction, replying with an error if the creation // was not successful. createdTx, err := w.CreateSimpleTx(account, outputs, minconf, satPerKb) if err != nil { return nil, err } // Create transaction record and insert into the db. rec, err := wtxmgr.NewTxRecordFromMsgTx(createdTx.Tx, time.Now()) if err != nil { log.Errorf("Cannot create record for created transaction: %v", err) return nil, err } err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) err := w.TxStore.InsertTx(txmgrNs, rec, nil) if err != nil { return err } if createdTx.ChangeIndex >= 0 { err = w.TxStore.AddCredit(txmgrNs, rec, nil, uint32(createdTx.ChangeIndex), true) if err != nil { log.Errorf("Error adding change address for sent "+ "tx: %v", err) return err } } return nil }) if err != nil { return nil, err } // TODO: The record already has the serialized tx, so no need to // serialize it again. return chainClient.SendRawTransaction(&rec.MsgTx, false) } // SignatureError records the underlying error when validating a transaction // input signature. type SignatureError struct { InputIndex uint32 Error error } // SignTransaction uses secrets of the wallet, as well as additional secrets // passed in by the caller, to create and add input signatures to a transaction. // // Transaction input script validation is used to confirm that all signatures // are valid. For any invalid input, a SignatureError is added to the returns. // The final error return is reserved for unexpected or fatal errors, such as // being unable to determine a previous output script to redeem. // // The transaction pointed to by tx is modified by this function. func (w *Wallet) SignTransaction(tx *wire.MsgTx, hashType txscript.SigHashType, additionalPrevScripts map[wire.OutPoint][]byte, additionalKeysByAddress map[string]*btcutil.WIF, p2shRedeemScriptsByAddress map[string][]byte) ([]SignatureError, error) { var signErrors []SignatureError err := walletdb.View(w.db, func(dbtx walletdb.ReadTx) error { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) for i, txIn := range tx.TxIn { prevOutScript, ok := additionalPrevScripts[txIn.PreviousOutPoint] if !ok { prevHash := &txIn.PreviousOutPoint.Hash prevIndex := txIn.PreviousOutPoint.Index txDetails, err := w.TxStore.TxDetails(txmgrNs, prevHash) if err != nil { return fmt.Errorf("cannot query previous transaction "+ "details for %v: %v", txIn.PreviousOutPoint, err) } if txDetails == nil { return fmt.Errorf("%v not found", txIn.PreviousOutPoint) } prevOutScript = txDetails.MsgTx.TxOut[prevIndex].PkScript } // Set up our callbacks that we pass to txscript so it can // look up the appropriate keys and scripts by address. getKey := txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, bool, error) { if len(additionalKeysByAddress) != 0 { addrStr := addr.EncodeAddress() wif, ok := additionalKeysByAddress[addrStr] if !ok { return nil, false, errors.New("no key for address") } return wif.PrivKey, wif.CompressPubKey, nil } address, err := w.Manager.Address(addrmgrNs, addr) if err != nil { return nil, false, err } pka, ok := address.(waddrmgr.ManagedPubKeyAddress) if !ok { return nil, false, fmt.Errorf("address %v is not "+ "a pubkey address", address.Address().EncodeAddress()) } key, err := pka.PrivKey() if err != nil { return nil, false, err } return key, pka.Compressed(), nil }) getScript := txscript.ScriptClosure(func(addr btcutil.Address) ([]byte, error) { // If keys were provided then we can only use the // redeem scripts provided with our inputs, too. if len(additionalKeysByAddress) != 0 { addrStr := addr.EncodeAddress() script, ok := p2shRedeemScriptsByAddress[addrStr] if !ok { return nil, errors.New("no script for address") } return script, nil } address, err := w.Manager.Address(addrmgrNs, addr) if err != nil { return nil, err } sa, ok := address.(waddrmgr.ManagedScriptAddress) if !ok { return nil, errors.New("address is not a script" + " address") } return sa.Script() }) // SigHashSingle inputs can only be signed if there's a // corresponding output. However this could be already signed, // so we always verify the output. if (hashType&txscript.SigHashSingle) != txscript.SigHashSingle || i < len(tx.TxOut) { script, err := txscript.SignTxOutput(w.ChainParams(), tx, i, prevOutScript, hashType, getKey, getScript, txIn.SignatureScript) // Failure to sign isn't an error, it just means that // the tx isn't complete. if err != nil { signErrors = append(signErrors, SignatureError{ InputIndex: uint32(i), Error: err, }) continue } txIn.SignatureScript = script } // Either it was already signed or we just signed it. // Find out if it is completely satisfied or still needs more. vm, err := txscript.NewEngine(prevOutScript, tx, i, txscript.StandardVerifyFlags, nil, nil, 0) if err == nil { err = vm.Execute() } if err != nil { signErrors = append(signErrors, SignatureError{ InputIndex: uint32(i), Error: err, }) } } return nil }) return signErrors, err } // PublishTransaction sends the transaction to the consensus RPC server so it // can be propagated to other nodes and eventually mined. // // This function is unstable and will be removed once syncing code is moved out // of the wallet. func (w *Wallet) PublishTransaction(tx *wire.MsgTx) error { server, err := w.requireChainClient() if err != nil { return err } switch server.(type) { // If our chain backend is neutrino, then we'll add this as an // unconfirmed transaction into the transaction store. Otherwise, we // won't eve be notified of it's acceptance, meaning we won't attempt // to re-broadcast. case *chain.NeutrinoClient: rec, err := wtxmgr.NewTxRecordFromMsgTx( tx, time.Now(), ) if err != nil { return err } err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { txmgrNs := tx.ReadWriteBucket(wtxmgrNamespaceKey) return w.TxStore.InsertTx(txmgrNs, rec, nil) }) if err != nil { return err } } _, err = server.SendRawTransaction(tx, false) return err } // ChainParams returns the network parameters for the blockchain the wallet // belongs to. func (w *Wallet) ChainParams() *chaincfg.Params { return w.chainParams } // Database returns the underlying walletdb database. This method is provided // in order to allow applications wrapping btcwallet to store app-specific data // with the wallet's database. func (w *Wallet) Database() walletdb.DB { return w.db } // Create creates an new wallet, writing it to an empty database. If the passed // seed is non-nil, it is used. Otherwise, a secure random seed of the // recommended length is generated. func Create(db walletdb.DB, pubPass, privPass, seed []byte, params *chaincfg.Params) error { // If a seed was provided, ensure that it is of valid length. Otherwise, // we generate a random seed for the wallet with the recommended seed // length. if seed == nil { hdSeed, err := hdkeychain.GenerateSeed( hdkeychain.RecommendedSeedLen) if err != nil { return err } seed = hdSeed } if len(seed) < hdkeychain.MinSeedBytes || len(seed) > hdkeychain.MaxSeedBytes { return hdkeychain.ErrInvalidSeedLen } return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { addrmgrNs, err := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) if err != nil { return err } txmgrNs, err := tx.CreateTopLevelBucket(wtxmgrNamespaceKey) if err != nil { return err } err = waddrmgr.Create(addrmgrNs, seed, pubPass, privPass, params, nil) if err != nil { return err } return wtxmgr.Create(txmgrNs) }) } // Open loads an already-created wallet from the passed database and namespaces. func Open(db walletdb.DB, pubPass []byte, cbs *waddrmgr.OpenCallbacks, params *chaincfg.Params) (*Wallet, error) { err := walletdb.View(db, func(tx walletdb.ReadTx) error { waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) if waddrmgrBucket == nil { return errors.New("missing address manager namespace") } wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) if wtxmgrBucket == nil { return errors.New("missing transaction manager namespace") } return nil }) if err != nil { return nil, err } // Perform upgrades as necessary. Each upgrade is done under its own // transaction, which is managed by each package itself, so the entire // DB is passed instead of passing already opened write transaction. // // This will need to change later when upgrades in one package depend on // data in another (such as removing chain synchronization from address // manager). err = waddrmgr.DoUpgrades(db, waddrmgrNamespaceKey, pubPass, params, cbs) if err != nil { return nil, err } err = wtxmgr.DoUpgrades(db, wtxmgrNamespaceKey) if err != nil { return nil, err } // Open database abstraction instances var ( addrMgr *waddrmgr.Manager txMgr *wtxmgr.Store ) err = walletdb.View(db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) var err error addrMgr, err = waddrmgr.Open(addrmgrNs, pubPass, params) if err != nil { return err } txMgr, err = wtxmgr.Open(txmgrNs, params) return err }) if err != nil { return nil, err } log.Infof("Opened wallet") // TODO: log balance? last sync height? w := &Wallet{ publicPassphrase: pubPass, db: db, Manager: addrMgr, TxStore: txMgr, lockedOutpoints: map[wire.OutPoint]struct{}{}, rescanAddJob: make(chan *RescanJob), rescanBatch: make(chan *rescanBatch), rescanNotifications: make(chan interface{}), rescanProgress: make(chan *RescanProgressMsg), rescanFinished: make(chan *RescanFinishedMsg), createTxRequests: make(chan createTxRequest), unlockRequests: make(chan unlockRequest), lockRequests: make(chan struct{}), holdUnlockRequests: make(chan chan heldUnlock), lockState: make(chan bool), changePassphrase: make(chan changePassphraseRequest), chainParams: params, quit: make(chan struct{}), } w.NtfnServer = newNotificationServer(w) w.TxStore.NotifyUnspent = func(hash *chainhash.Hash, index uint32) { w.NtfnServer.notifyUnspentOutput(0, hash, index) } return w, nil }