Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark addresses used #196

Closed
wants to merge 11 commits into from
45 changes: 11 additions & 34 deletions btcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ func walletMain() error {
}()
}

// Load the wallet database. It must have been created with the
// --create option already or this will return an appropriate error.
wallet, err := openWallet()
if err != nil {
log.Errorf("%v", err)
return err
}
defer wallet.db.Close()

// Create and start HTTP server to serve wallet client connections.
// This will be updated with the wallet and chain server RPC client
// created below after each is created.
Expand All @@ -78,6 +87,7 @@ func walletMain() error {
return err
}
server.Start()
server.SetWallet(wallet)

// Shutdown the server if an interrupt signal is received.
addInterruptHandler(server.Stop)
Expand Down Expand Up @@ -121,49 +131,16 @@ func walletMain() error {
chainSvrChan <- rpcc
}()

// Create a channel to report unrecoverable errors during the loading of
// the wallet files. These may include OS file handling errors or
// issues deserializing the wallet files, but does not include missing
// wallet files (as that must be handled by creating a new wallet).
walletOpenErrors := make(chan error)

go func() {
defer close(walletOpenErrors)

// Open wallet structures from disk.
w, err := openWallet()
if err != nil {
if os.IsNotExist(err) {
// If the keystore file is missing, notify the server
// that generating new wallets is ok.
server.SetWallet(nil)
return
}
// If the keystore file exists but another error was
// encountered, we cannot continue.
log.Errorf("Cannot load wallet files: %v", err)
walletOpenErrors <- err
return
}

server.SetWallet(w)

// Start wallet goroutines and handle RPC client notifications
// if the chain server connection was opened.
select {
case chainSvr := <-chainSvrChan:
w.Start(chainSvr)
wallet.Start(chainSvr)
case <-server.quit:
}
}()

// Check for unrecoverable errors during the wallet startup, and return
// the error, if any.
err, ok := <-walletOpenErrors
if ok {
return err
}

// Wait for the server to shutdown either due to a stop RPC request
// or an interrupt.
server.WaitForShutdown()
Expand Down
20 changes: 10 additions & 10 deletions chain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/keystore"
"github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcws"
)

Expand All @@ -38,7 +38,7 @@ type Client struct {

enqueueNotification chan interface{}
dequeueNotification chan interface{}
currentBlock chan *keystore.BlockStamp
currentBlock chan *waddrmgr.BlockStamp

// Notification channels regarding the state of the client. These exist
// so other components can listen in on chain activity. These are
Expand All @@ -64,7 +64,7 @@ func NewClient(chainParams *chaincfg.Params, connect, user, pass string, certs [
chainParams: chainParams,
enqueueNotification: make(chan interface{}),
dequeueNotification: make(chan interface{}),
currentBlock: make(chan *keystore.BlockStamp),
currentBlock: make(chan *waddrmgr.BlockStamp),
notificationLock: new(sync.Mutex),
quit: make(chan struct{}),
}
Expand Down Expand Up @@ -157,11 +157,11 @@ func (c *Client) WaitForShutdown() {
type (
// BlockConnected is a notification for a newly-attached block to the
// best chain.
BlockConnected keystore.BlockStamp
BlockConnected waddrmgr.BlockStamp

// BlockDisconnected is a notifcation that the block described by the
// BlockStamp was reorganized out of the best chain.
BlockDisconnected keystore.BlockStamp
BlockDisconnected waddrmgr.BlockStamp

// RecvTx is a notification for a transaction which pays to a wallet
// address.
Expand Down Expand Up @@ -204,7 +204,7 @@ func (c *Client) Notifications() <-chan interface{} {

// BlockStamp returns the latest block notified by the client, or an error
// if the client has been shut down.
func (c *Client) BlockStamp() (*keystore.BlockStamp, error) {
func (c *Client) BlockStamp() (*waddrmgr.BlockStamp, error) {
select {
case bs := <-c.currentBlock:
return bs, nil
Expand Down Expand Up @@ -238,11 +238,11 @@ func (c *Client) onClientConnect() {
}

func (c *Client) onBlockConnected(hash *wire.ShaHash, height int32) {
c.enqueueNotification <- BlockConnected{Hash: hash, Height: height}
c.enqueueNotification <- BlockConnected{Hash: *hash, Height: height}
}

func (c *Client) onBlockDisconnected(hash *wire.ShaHash, height int32) {
c.enqueueNotification <- BlockDisconnected{Hash: hash, Height: height}
c.enqueueNotification <- BlockDisconnected{Hash: *hash, Height: height}
}

func (c *Client) onRecvTx(tx *btcutil.Tx, block *btcws.BlockDetails) {
Expand Down Expand Up @@ -294,7 +294,7 @@ func (c *Client) handler() {
c.wg.Done()
}

bs := &keystore.BlockStamp{Hash: hash, Height: height}
bs := &waddrmgr.BlockStamp{Hash: *hash, Height: height}

// TODO: Rather than leaving this as an unbounded queue for all types of
// notifications, try dropping ones where a later enqueued notification
Expand Down Expand Up @@ -329,7 +329,7 @@ out:

case dequeue <- next:
if n, ok := next.(BlockConnected); ok {
bs = (*keystore.BlockStamp)(&n)
bs = (*waddrmgr.BlockStamp)(&n)
}

notifications[0] = nil
Expand Down
41 changes: 25 additions & 16 deletions chainntfns.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/keystore"
"github.com/btcsuite/btcwallet/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
)

func (w *Wallet) handleChainNotifications() {
for n := range w.chainSvr.Notifications() {
var err error
switch n := n.(type) {
case chain.BlockConnected:
w.connectBlock(keystore.BlockStamp(n))
w.connectBlock(waddrmgr.BlockStamp(n))
case chain.BlockDisconnected:
w.disconnectBlock(keystore.BlockStamp(n))
w.disconnectBlock(waddrmgr.BlockStamp(n))
case chain.RecvTx:
err = w.addReceivedTx(n.Tx, n.Block)
case chain.RedeemingTx:
Expand All @@ -53,13 +53,16 @@ func (w *Wallet) handleChainNotifications() {
// connectBlock handles a chain server notification by marking a wallet
// that's currently in-sync with the chain server as being synced up to
// the passed block.
func (w *Wallet) connectBlock(bs keystore.BlockStamp) {
func (w *Wallet) connectBlock(bs waddrmgr.BlockStamp) {
if !w.ChainSynced() {
return
}

w.KeyStore.SetSyncedWith(&bs)
w.KeyStore.MarkDirty()
if err := w.Manager.SetSyncedTo(&bs); err != nil {
log.Errorf("failed to update address manager sync state in "+
"connect block for hash %v (height %d): %v", bs.Hash,
bs.Height, err)
}
w.notifyConnectedBlock(bs)

w.notifyBalances(bs.Height)
Expand All @@ -68,22 +71,26 @@ func (w *Wallet) connectBlock(bs keystore.BlockStamp) {
// disconnectBlock handles a chain server reorganize by rolling back all
// block history from the reorged block for a wallet in-sync with the chain
// server.
func (w *Wallet) disconnectBlock(bs keystore.BlockStamp) {
func (w *Wallet) disconnectBlock(bs waddrmgr.BlockStamp) {
if !w.ChainSynced() {
return
}

// Disconnect the last seen block from the keystore if it
// matches the removed block.
iter := w.KeyStore.NewIterateRecentBlocks()
if iter != nil && *iter.BlockStamp().Hash == *bs.Hash {
// Disconnect the last seen block from the manager if it matches the
// removed block.
iter := w.Manager.NewIterateRecentBlocks()
if iter != nil && iter.BlockStamp().Hash == bs.Hash {
if iter.Prev() {
prev := iter.BlockStamp()
w.KeyStore.SetSyncedWith(&prev)
w.Manager.SetSyncedTo(&prev)
} else {
w.KeyStore.SetSyncedWith(nil)
// The reorg is farther back than the recently-seen list
// of blocks has recorded, so set it to unsynced which
// will in turn lead to a rescan from either the
// earliest blockstamp the addresses in the manager are
// known to have been created.
w.Manager.SetSyncedTo(nil)
}
w.KeyStore.MarkDirty()
}
w.notifyDisconnectedBlock(bs)

Expand All @@ -103,7 +110,7 @@ func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error {
activeNet.Params)
insert := false
for _, addr := range addrs {
_, err := w.KeyStore.Address(addr)
_, err := w.Manager.Address(addr)
if err == nil {
insert = true
break
Expand All @@ -129,6 +136,9 @@ func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error {
return err
}
w.TxStore.MarkDirty()
if err := w.markAddrsUsed(txr); err != nil {
return err
}
}
}

Expand All @@ -150,7 +160,6 @@ func (w *Wallet) addRedeemingTx(tx *btcutil.Tx, block *txstore.Block) error {
if _, err := txr.AddDebits(); err != nil {
return err
}
w.KeyStore.MarkDirty()

bs, err := w.chainSvr.BlockStamp()
if err == nil {
Expand Down
65 changes: 56 additions & 9 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,22 @@ import (

"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/keystore"
flags "github.com/btcsuite/go-flags"
)

const (
defaultCAFilename = "btcd.cert"
defaultConfigFilename = "btcwallet.conf"
defaultBtcNet = wire.TestNet3
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "btcwallet.log"
defaultDisallowFree = false
defaultRPCMaxClients = 10
defaultRPCMaxWebsockets = 25
defaultCAFilename = "btcd.cert"
defaultConfigFilename = "btcwallet.conf"
defaultBtcNet = wire.TestNet3
defaultLogLevel = "info"
defaultLogDirname = "logs"
defaultLogFilename = "btcwallet.log"
defaultDisallowFree = false
defaultRPCMaxClients = 10
defaultRPCMaxWebsockets = 25
walletDbName = "wallet.db"
walletDbWatchingOnlyName = "wowallet.db"
)

var (
Expand All @@ -54,6 +57,7 @@ var (

type config struct {
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
Create bool `long:"create" description:"Create the wallet if it does not exist"`
CAFile string `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of btcd RPC server to connect to (default localhost:18334, mainnet: localhost:8334, simnet: localhost:18556)"`
DebugLevel string `short:"d" long:"debuglevel" description:"Logging level {trace, debug, info, warn, error, critical}"`
Expand All @@ -65,6 +69,7 @@ type config struct {
Password string `short:"P" long:"password" default-mask:"-" description:"Password for client and btcd authorization"`
BtcdUsername string `long:"btcdusername" description:"Alternative username for btcd authorization"`
BtcdPassword string `long:"btcdpassword" default-mask:"-" description:"Alternative password for btcd authorization"`
WalletPass string `long:"walletpass" default-mask:"-" description:"The public wallet password -- Only required if the wallet was created with one"`
RPCCert string `long:"rpccert" description:"File containing the certificate file"`
RPCKey string `long:"rpckey" description:"File containing the certificate key"`
RPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of RPC clients for standard connections"`
Expand Down Expand Up @@ -242,6 +247,7 @@ func loadConfig() (*config, []string, error) {
ConfigFile: defaultConfigFile,
DataDir: defaultDataDir,
LogDir: defaultLogDir,
WalletPass: defaultPubPassphrase,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
DisallowFree: defaultDisallowFree,
Expand Down Expand Up @@ -360,6 +366,47 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err
}

// Ensure the wallet exists or create it when the create flag is set.
netDir := networkDir(cfg.DataDir, activeNet.Params)
dbPath := filepath.Join(netDir, walletDbName)
if cfg.Create {
// Error if the create flag is set and the wallet already
// exists.
if fileExists(dbPath) {
err := fmt.Errorf("The wallet already exists.")
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
}

// Ensure the data directory for the network exists.
if err := checkCreateDir(netDir); err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
}

// Perform the initial wallet creation wizard.
if err := createWallet(&cfg); err != nil {
fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
return nil, nil, err
}

// Created successfully, so exit now with success.
os.Exit(0)

} else if !fileExists(dbPath) {
var err error
keystorePath := filepath.Join(netDir, keystore.Filename)
if !fileExists(keystorePath) {
err = fmt.Errorf("The wallet does not exist. Run with the " +
"--create option to initialize and create it.")
} else {
err = fmt.Errorf("The wallet is in legacy format. Run with the " +
"--create option to import it.")
}
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
}

if cfg.RPCConnect == "" {
cfg.RPCConnect = activeNet.connect
}
Expand Down
Loading