diff --git a/waddrmgr/address.go b/waddrmgr/address.go index 5e0ceb8..badc087 100644 --- a/waddrmgr/address.go +++ b/waddrmgr/address.go @@ -53,7 +53,7 @@ type ManagedAddress interface { Compressed() bool // Used returns true if the backing address has been used in a transaction. - Used() bool + Used() (bool, error) } // ManagedPubKeyAddress extends ManagedAddress and additionally provides the @@ -191,8 +191,8 @@ func (a *managedAddress) Compressed() bool { // Used returns true if the address has been used in a transaction. // // This is part of the ManagedAddress interface implementation. -func (a *managedAddress) Used() bool { - return a.used +func (a *managedAddress) Used() (bool, error) { + return a.manager.fetchUsed(a.AddrHash()) } // PubKey returns the public key associated with the address. @@ -456,8 +456,8 @@ func (a *scriptAddress) Compressed() bool { // Used returns true if the address has been used in a transaction. // // This is part of the ManagedAddress interface implementation. -func (a *scriptAddress) Used() bool { - return a.used +func (a *scriptAddress) Used() (bool, error) { + return a.manager.fetchUsed(a.AddrHash()) } // Script returns the script associated with the address. @@ -484,7 +484,7 @@ func (a *scriptAddress) Script() ([]byte, error) { } // newScriptAddress initializes and returns a new pay-to-script-hash address. -func newScriptAddress(m *Manager, account uint32, scriptHash, scriptEncrypted []byte, used bool) (*scriptAddress, error) { +func newScriptAddress(m *Manager, account uint32, scriptHash, scriptEncrypted []byte) (*scriptAddress, error) { address, err := btcutil.NewAddressScriptHashFromHash(scriptHash, m.chainParams) if err != nil { @@ -496,6 +496,5 @@ func newScriptAddress(m *Manager, account uint32, scriptHash, scriptEncrypted [] account: account, address: address, scriptEncrypted: scriptEncrypted, - used: used, }, nil } diff --git a/waddrmgr/db.go b/waddrmgr/db.go index 836b9cc..717d4bf 100644 --- a/waddrmgr/db.go +++ b/waddrmgr/db.go @@ -113,7 +113,6 @@ type dbAddressRow struct { account uint32 addTime uint64 syncStatus syncStatus - used bool rawData []byte // Varies based on address type field. } @@ -987,17 +986,6 @@ func serializeScriptAddress(encryptedHash, encryptedScript []byte) []byte { return rawData } -// fetchAddressUsed returns true if the provided address hash was flagged as used. -func fetchAddressUsed(tx walletdb.Tx, addrHash []byte) bool { - bucket := tx.RootBucket().Bucket(usedAddrBucketName) - - val := bucket.Get(addrHash[:]) - if val != nil { - return true - } - return false -} - // fetchAddressByHash loads address information for the provided address hash // from the database. The returned value is one of the address rows for the // specific address type. The caller should use type assertions to ascertain @@ -1016,7 +1004,6 @@ func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) { if err != nil { return nil, err } - row.used = fetchAddressUsed(tx, addrHash[:]) switch row.addrType { case adtChain: @@ -1031,6 +1018,14 @@ func fetchAddressByHash(tx walletdb.Tx, addrHash []byte) (interface{}, error) { return nil, managerError(ErrDatabase, str, nil) } +// fetchAddressUsed returns true if the provided address id was flagged as used. +func fetchAddressUsed(tx walletdb.Tx, addressID []byte) bool { + bucket := tx.RootBucket().Bucket(usedAddrBucketName) + + addrHash := fastsha256.Sum256(addressID) + return bucket.Get(addrHash[:]) != nil +} + // markAddressUsed flags the provided address id as used in the database. func markAddressUsed(tx walletdb.Tx, addressID []byte) error { bucket := tx.RootBucket().Bucket(usedAddrBucketName) diff --git a/waddrmgr/manager.go b/waddrmgr/manager.go index 4b2c2a5..ded3656 100644 --- a/waddrmgr/manager.go +++ b/waddrmgr/manager.go @@ -374,7 +374,7 @@ func (m *Manager) Close() error { // The passed derivedKey is zeroed after the new address is created. // // This function MUST be called with the manager lock held for writes. -func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, account, branch, index uint32, used bool) (ManagedAddress, error) { +func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, account, branch, index uint32) (ManagedAddress, error) { // Create a new managed address based on the public or private key // depending on whether the passed key is private. Also, zero the // key after creating the managed address from it. @@ -397,7 +397,6 @@ func (m *Manager) keyToManaged(derivedKey *hdkeychain.ExtendedKey, account, bran if branch == internalBranch { ma.internal = true } - ma.used = used return ma, nil } @@ -512,7 +511,7 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) { if err != nil { return nil, err } - lastExtAddr, err := m.keyToManaged(lastExtKey, account, branch, index, false) + lastExtAddr, err := m.keyToManaged(lastExtKey, account, branch, index) if err != nil { return nil, err } @@ -527,7 +526,7 @@ func (m *Manager) loadAccountInfo(account uint32) (*accountInfo, error) { if err != nil { return nil, err } - lastIntAddr, err := m.keyToManaged(lastIntKey, account, branch, index, false) + lastIntAddr, err := m.keyToManaged(lastIntKey, account, branch, index) if err != nil { return nil, err } @@ -563,7 +562,7 @@ func (m *Manager) chainAddressRowToManaged(row *dbChainAddressRow) (ManagedAddre return nil, err } - return m.keyToManaged(addressKey, row.account, row.branch, row.index, row.used) + return m.keyToManaged(addressKey, row.account, row.branch, row.index) } // importedAddressRowToManaged returns a new managed address based on imported @@ -590,7 +589,6 @@ func (m *Manager) importedAddressRowToManaged(row *dbImportedAddressRow) (Manage } ma.privKeyEncrypted = row.encryptedPrivKey ma.imported = true - ma.used = row.used return ma, nil } @@ -605,7 +603,7 @@ func (m *Manager) scriptAddressRowToManaged(row *dbScriptAddressRow) (ManagedAdd return nil, managerError(ErrCrypto, str, err) } - return newScriptAddress(m, row.account, scriptHash, row.encryptedScript, row.used) + return newScriptAddress(m, row.account, scriptHash, row.encryptedScript) } // rowInterfaceToManaged returns a new managed address based on the given @@ -1173,7 +1171,7 @@ func (m *Manager) ImportScript(script []byte, bs *BlockStamp) (ManagedScriptAddr // since it will be cleared on lock and the script the caller passed // should not be cleared out from under the caller. scriptAddr, err := newScriptAddress(m, ImportedAddrAccount, scriptHash, - encryptedScript, false) + encryptedScript) if err != nil { return nil, err } @@ -1360,15 +1358,26 @@ func (m *Manager) Unlock(passphrase []byte) error { return nil } -// MarkUsed updates the used flag for the provided address id. -func (m *Manager) MarkUsed(addressID []byte) error { +// fetchUsed returns true if the provided address id was flagged used. +func (m *Manager) fetchUsed(addressID []byte) (bool, error) { + var used bool + err := m.namespace.View(func(tx walletdb.Tx) error { + used = fetchAddressUsed(tx, addressID) + return nil + }) + return used, err +} + +// MarkUsed updates the used flag for the provided address. +func (m *Manager) MarkUsed(address btcutil.Address) error { + addressID := address.ScriptAddress() err := m.namespace.Update(func(tx walletdb.Tx) error { return markAddressUsed(tx, addressID) }) if err != nil { return maybeConvertDbError(err) } - // 'used' flag has become stale so remove key from cache + // Clear caches which might have stale entries for used addresses delete(m.addrs, addrKey(addressID)) return nil } @@ -1580,7 +1589,11 @@ func (m *Manager) LastExternalAddress(account uint32) (ManagedAddress, error) { return nil, err } - return acctInfo.lastExternalAddr, nil + if acctInfo.nextExternalIndex > 0 { + return acctInfo.lastExternalAddr, nil + } + + return nil, managerError(ErrAddressNotFound, "no previous external address", nil) } // LastInternalAddress returns the most recently requested chained internal @@ -1608,7 +1621,11 @@ func (m *Manager) LastInternalAddress(account uint32) (ManagedAddress, error) { return nil, err } - return acctInfo.lastInternalAddr, nil + if acctInfo.nextInternalIndex > 0 { + return acctInfo.lastInternalAddr, nil + } + + return nil, managerError(ErrAddressNotFound, "no previous internal address", nil) } // ValidateAccountName validates the given account name and returns an error, if any. diff --git a/waddrmgr/manager_test.go b/waddrmgr/manager_test.go index 77a24a8..f055378 100644 --- a/waddrmgr/manager_test.go +++ b/waddrmgr/manager_test.go @@ -1028,28 +1028,37 @@ func testMarkUsed(tc *testContext) bool { addrHash := expectedAddr1.addressHash addr, err := btcutil.NewAddressPubKeyHash(addrHash, chainParams) - if tc.create { - // Test that initially the address is not flagged as used - maddr, err := tc.manager.Address(addr) - if err != nil { - tc.t.Errorf("%s: unexpected error: %v", prefix, err) - } - if maddr.Used() != false { - tc.t.Errorf("%v: unexpected used flag -- got "+ - "%v, want %v", prefix, maddr.Used(), expectedAddr1.used) - } - } - err = tc.manager.MarkUsed(addrHash) - if err != nil { - tc.t.Errorf("%s: unexpected error: %v", prefix, err) - } maddr, err := tc.manager.Address(addr) if err != nil { tc.t.Errorf("%s: unexpected error: %v", prefix, err) + return false } - if maddr.Used() != expectedAddr1.used { + if tc.create { + // Test that initially the address is not flagged as used + used, err := maddr.Used() + if err != nil { + tc.t.Errorf("%s: unexpected error: %v", prefix, err) + return false + } + if used != false { + tc.t.Errorf("%v: unexpected used flag -- got "+ + "%v, want %v", prefix, used, expectedAddr1.used) + return false + } + } + err = tc.manager.MarkUsed(addr) + if err != nil { + tc.t.Errorf("%s: unexpected error: %v", prefix, err) + return false + } + used, err := maddr.Used() + if err != nil { + tc.t.Errorf("%s: unexpected error: %v", prefix, err) + return false + } + if used != expectedAddr1.used { tc.t.Errorf("%v: unexpected used flag -- got "+ - "%v, want %v", prefix, maddr.Used(), expectedAddr1.used) + "%v, want %v", prefix, used, expectedAddr1.used) } return true } diff --git a/wallet/wallet.go b/wallet/wallet.go index c7609e2..ad94c99 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -262,8 +262,7 @@ func (w *Wallet) markAddrsUsed(t *txstore.TxRecord) error { // range below does nothing. _, addrs, _, _ := c.Addresses(w.chainParams) for _, addr := range addrs { - addressID := addr.ScriptAddress() - if err := w.Manager.MarkUsed(addressID); err != nil { + if err := w.Manager.MarkUsed(addr); err != nil { return err } log.Infof("Marked address used %s", addr.EncodeAddress()) @@ -715,14 +714,6 @@ func (w *Wallet) diskWriter() { w.wg.Done() } -// AddressUsed returns whether there are any recorded transactions spending to -// a given address. Assumming correct TxStore usage, this will return true iff -// there are any transactions with outputs to this address in the blockchain or -// the btcd mempool. -func (w *Wallet) AddressUsed(addr waddrmgr.ManagedAddress) bool { - return addr.Used() -} - // 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. @@ -732,7 +723,11 @@ func (w *Wallet) AccountUsed(account uint32) (bool, error) { return false, err } for _, addr := range addrs { - if w.AddressUsed(addr) { + used, err := addr.Used() + if err != nil { + return false, err + } + if used { return true, nil } } @@ -791,11 +786,20 @@ func (w *Wallet) CalculateAccountBalance(account uint32, confirms int) (btcutil. func (w *Wallet) CurrentAddress(account uint32) (btcutil.Address, error) { addr, err := w.Manager.LastExternalAddress(account) if err != nil { + // If no address exists yet, create the first external address + merr, ok := err.(waddrmgr.ManagerError) + if ok && merr.ErrorCode == waddrmgr.ErrAddressNotFound { + return w.NewAddress(account) + } return nil, err } // Get next chained address if the last one has already been used. - if w.AddressUsed(addr) { + used, err := addr.Used() + if err != nil { + return nil, err + } + if used { return w.NewAddress(account) }