Skip to content

Commit

Permalink
Refactor address pool code and automatically resync accounts from seed
Browse files Browse the repository at this point in the history
The old address pool code was faulty in that it could not scale to
more accounts than the default account and did address index alignment
using an actual address instead of an index. This updates the address
pools to be generalized so that in the future they can be multiaccount.
Address indexes for an account are now stored in the meta bucket of the
address manager database. The logic for the address pool has been
greatly simplified.

Accounts are now restored when the wallet is restored from seed. New
logic similar to the logarithmic scan for addresses in the default
account has been added. On start up, the wallet will also rescan and
check for new address usage in all of its used accounts. On first start
up, the wallet must be unlocked so that accounts can be added if
necessary. New logic prompts the user for the password.

A new flag, --promptpass, has been added. This will cause the wallet
to prompt the user for the password on startup, leaving the wallet
unlocked indefinitely.
  • Loading branch information
cjepson committed Mar 31, 2016
1 parent 1a89f7e commit 4b64adf
Show file tree
Hide file tree
Showing 13 changed files with 1,205 additions and 1,186 deletions.
70 changes: 40 additions & 30 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
defaultTicketMaxPrice = 50.0
defaultAutomaticRepair = false
defaultUnsafeMainNet = false
defaultPromptPass = false

walletDbName = "wallet.db"
)
Expand Down Expand Up @@ -74,6 +75,7 @@ type config struct {

// Wallet options
WalletPass string `long:"walletpass" default-mask:"-" description:"The public wallet password -- Only required if the wallet was created with one"`
PromptPass bool `long:"promptpass" description:"The private wallet password is prompted for at start up, so the wallet starts unlocked without a time limit"`
DisallowFree bool `long:"disallowfree" description:"Force transactions to always include a fee"`
EnableStakeMining bool `long:"enablestakemining" description:"Enable stake mining"`
VoteBits uint16 `long:"votebits" description:"Set your stake mining votebits to value (default: 0xFFFF)"`
Expand Down Expand Up @@ -229,14 +231,22 @@ func parseAndSetDebugLevels(debugLevel string) error {
// The above results in dcrwallet functioning properly without any config
// settings while still allowing the user to override settings with config files
// and command line options. Command line options always take precedence.
// The bool returned indicates whether or not the wallet was recreated from a
// seed and needs to perform the initial resync. The []byte is the private
// passphrase required to do the sync for this special case.
func loadConfig() (*config, []string, error) {
loadConfigError := func(err error) (*config, []string, error) {
return nil, nil, err
}

// Default config.
cfg := config{
DebugLevel: defaultLogLevel,
ConfigFile: defaultConfigFile,
DataDir: defaultDataDir,
LogDir: defaultLogDir,
WalletPass: wallet.InsecurePubPassphrase,
PromptPass: defaultPromptPass,
RPCKey: defaultRPCKeyFile,
RPCCert: defaultRPCCertFile,
LegacyRPCMaxClients: defaultRPCMaxClients,
Expand All @@ -256,7 +266,7 @@ func loadConfig() (*config, []string, error) {
exists, err := cfgutil.FileExists(defaultConfigFilename)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}
if exists {
cfg.ConfigFile = defaultConfigFile
Expand All @@ -271,7 +281,7 @@ func loadConfig() (*config, []string, error) {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
preParser.WriteHelp(os.Stderr)
}
return nil, nil, err
return loadConfigError(err)
}

// Show the version and exit if the version flag was specified.
Expand All @@ -292,7 +302,7 @@ func loadConfig() (*config, []string, error) {
if _, ok := err.(*os.PathError); !ok {
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
return loadConfigError(err)
}
configFileError = err
}
Expand All @@ -303,7 +313,7 @@ func loadConfig() (*config, []string, error) {
if e, ok := err.(*flags.Error); !ok || e.Type != flags.ErrHelp {
parser.WriteHelp(os.Stderr)
}
return nil, nil, err
return loadConfigError(err)
}

// Warn about missing config file after the final command line parse
Expand Down Expand Up @@ -342,7 +352,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf(str, "loadConfig")
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
return loadConfigError(err)
}

// Append the network type to the log directory so it is "namespaced"
Expand All @@ -365,7 +375,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf("%s: %v", "loadConfig", err.Error())
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
return loadConfigError(err)
}

// Exit if you try to use a simulation wallet with a standard
Expand Down Expand Up @@ -400,13 +410,13 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf("The flags --create and --createtemp can not " +
"be specified together. Use --help for more information.")
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}

dbFileExists, err := cfgutil.FileExists(dbPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}

if cfg.CreateTemp {
Expand All @@ -422,14 +432,14 @@ func loadConfig() (*config, []string, error) {
// Ensure the data directory for the network exists.
if err := checkCreateDir(netDir); err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}

if !tempWalletExists {
// Perform the initial wallet creation wizard.
if err := createSimulationWallet(&cfg); err != nil {
fmt.Fprintln(os.Stderr, "Unable to create wallet:", err)
return nil, nil, err
return loadConfigError(err)
}
}
} else if cfg.Create {
Expand All @@ -439,25 +449,25 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf("The wallet database file `%v` "+
"already exists.", dbPath)
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}

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

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

Expand All @@ -468,7 +478,7 @@ func loadConfig() (*config, []string, error) {
keystoreExists, err := cfgutil.FileExists(keystorePath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}
if !keystoreExists {
err = fmt.Errorf("The wallet does not exist. Run with the " +
Expand All @@ -478,7 +488,7 @@ func loadConfig() (*config, []string, error) {
"--create option to import it.")
}
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}

if len(cfg.TicketAddress) != 0 {
Expand All @@ -488,7 +498,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf(str, funcName, cfg.TicketAddress, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
return loadConfigError(err)
}
}

Expand All @@ -502,7 +512,7 @@ func loadConfig() (*config, []string, error) {
if err != nil {
fmt.Fprintf(os.Stderr,
"Invalid rpcconnect network address: %v\n", err)
return nil, nil, err
return loadConfigError(err)
}

localhostListeners := map[string]struct{}{
Expand All @@ -512,7 +522,7 @@ func loadConfig() (*config, []string, error) {
}
RPCHost, _, err := net.SplitHostPort(cfg.RPCConnect)
if err != nil {
return nil, nil, err
return loadConfigError(err)
}
if cfg.DisableClientTLS {
if _, ok := localhostListeners[RPCHost]; !ok {
Expand All @@ -522,7 +532,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf(str, funcName, cfg.RPCConnect)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
return loadConfigError(err)
}
} else {
// If CAFile is unset, choose either the copy or local dcrd cert.
Expand All @@ -534,15 +544,15 @@ func loadConfig() (*config, []string, error) {
certExists, err := cfgutil.FileExists(cfg.CAFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}
if !certExists {
if _, ok := localhostListeners[RPCHost]; ok {
dcrdCertExists, err := cfgutil.FileExists(
dcrdHomedirCAFile)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}
if dcrdCertExists {
cfg.CAFile = dcrdHomedirCAFile
Expand All @@ -560,7 +570,7 @@ func loadConfig() (*config, []string, error) {
if len(cfg.ExperimentalRPCListeners) == 0 && len(cfg.LegacyRPCListeners) == 0 {
addrs, err := net.LookupHost("localhost")
if err != nil {
return nil, nil, err
return loadConfigError(err)
}
cfg.LegacyRPCListeners = make([]string, 0, len(addrs))
for _, addr := range addrs {
Expand All @@ -576,14 +586,14 @@ func loadConfig() (*config, []string, error) {
if err != nil {
fmt.Fprintf(os.Stderr,
"Invalid network address in legacy RPC listeners: %v\n", err)
return nil, nil, err
return loadConfigError(err)
}
cfg.ExperimentalRPCListeners, err = cfgutil.NormalizeAddresses(
cfg.ExperimentalRPCListeners, activeNet.RPCServerPort)
if err != nil {
fmt.Fprintf(os.Stderr,
"Invalid network address in RPC listeners: %v\n", err)
return nil, nil, err
return loadConfigError(err)
}

// Both RPC servers may not listen on the same interface/port.
Expand All @@ -599,7 +609,7 @@ func loadConfig() (*config, []string, error) {
"used as a listener address for both "+
"RPC servers", addr)
fmt.Fprintln(os.Stderr, err)
return nil, nil, err
return loadConfigError(err)
}
}
}
Expand All @@ -617,7 +627,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf(str, funcName, addr, err)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
return loadConfigError(err)
}
if _, ok := localhostListeners[host]; !ok {
str := "%s: the --noservertls option may not be used " +
Expand All @@ -626,7 +636,7 @@ func loadConfig() (*config, []string, error) {
err := fmt.Errorf(str, funcName, addr)
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, usageMessage)
return nil, nil, err
return loadConfigError(err)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion dcrwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func walletMain() error {
TicketAddress: cfg.TicketAddress,
TicketMaxPrice: cfg.TicketMaxPrice,
}
loader := wallet.NewLoader(activeNet.Params, dbDir, stakeOptions, cfg.AutomaticRepair, cfg.UnsafeMainNet)
loader := wallet.NewLoader(activeNet.Params, dbDir, stakeOptions, cfg.AutomaticRepair, cfg.UnsafeMainNet, cfg.PromptPass)

// Create and start HTTP server to serve wallet client connections.
// This will be updated with the wallet and chain server RPC client
Expand Down
13 changes: 7 additions & 6 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ func promptListBool(reader *bufio.Reader, prefix string, defaultEntry string) (b
return response == "yes" || response == "y", nil
}

// promptPass prompts the user for a passphrase with the given prefix. The
// PromptPass prompts the user for a passphrase with the given prefix. The
// function will ask the user to confirm the passphrase and will repeat the
// prompts until they enter a matching response.
func promptPass(reader *bufio.Reader, prefix string, confirm bool) ([]byte, error) {
func PromptPass(reader *bufio.Reader, prefix string, confirm bool) ([]byte, error) {
// Prompt the user until they enter a passphrase.
prompt := fmt.Sprintf("%s: ", prefix)
for {
Expand Down Expand Up @@ -161,7 +161,7 @@ func PrivatePass(reader *bufio.Reader, legacyKeyStore *keystore.Store) ([]byte,
// When there is not an existing legacy wallet, simply prompt the user
// for a new private passphase and return it.
if legacyKeyStore == nil {
return promptPass(reader, "Enter the private "+
return PromptPass(reader, "Enter the private "+
"passphrase for your new wallet", true)
}

Expand All @@ -172,7 +172,7 @@ func PrivatePass(reader *bufio.Reader, legacyKeyStore *keystore.Store) ([]byte,
"your existing legacy wallet will be imported into the new " +
"wallet format.")
for {
privPass, err := promptPass(reader, "Enter the private "+
privPass, err := PromptPass(reader, "Enter the private "+
"passphrase for your existing wallet", false)
if err != nil {
return nil, err
Expand Down Expand Up @@ -228,7 +228,7 @@ func PublicPass(reader *bufio.Reader, privPass []byte,
}

for {
pubPass, err = promptPass(reader, "Enter the public "+
pubPass, err = PromptPass(reader, "Enter the public "+
"passphrase for your new wallet", true)
if err != nil {
return nil, err
Expand Down Expand Up @@ -261,7 +261,8 @@ func PublicPass(reader *bufio.Reader, privPass []byte,
// seed. When the user answers no, a seed will be generated and displayed to
// the user along with prompting them for confirmation. When the user answers
// yes, a the user is prompted for it. All prompts are repeated until the user
// enters a valid response.
// enters a valid response. The bool returned indicates if the wallet was
// restored from a given seed or not.
func Seed(reader *bufio.Reader) ([]byte, error) {
// Ascertain the wallet generation seed.
useUserSeed, err := promptListBool(reader, "Do you have an "+
Expand Down
Loading

0 comments on commit 4b64adf

Please sign in to comment.