Accept feexpub once at startup. (#97)
This commit is contained in:
parent
dcfc2e969d
commit
d407af35c0
49
config.go
49
config.go
@ -15,6 +15,7 @@ import (
|
|||||||
"decred.org/dcrwallet/wallet/txrules"
|
"decred.org/dcrwallet/wallet/txrules"
|
||||||
"github.com/decred/dcrd/dcrutil/v3"
|
"github.com/decred/dcrd/dcrutil/v3"
|
||||||
"github.com/decred/dcrd/hdkeychain/v3"
|
"github.com/decred/dcrd/hdkeychain/v3"
|
||||||
|
"github.com/decred/vspd/database"
|
||||||
flags "github.com/jessevdk/go-flags"
|
flags "github.com/jessevdk/go-flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,10 +39,7 @@ type config struct {
|
|||||||
Listen string `long:"listen" ini-name:"listen" description:"The ip:port to listen for API requests."`
|
Listen string `long:"listen" ini-name:"listen" description:"The ip:port to listen for API requests."`
|
||||||
LogLevel string `long:"loglevel" ini-name:"loglevel" description:"Logging level." choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"critical"`
|
LogLevel string `long:"loglevel" ini-name:"loglevel" description:"Logging level." choice:"trace" choice:"debug" choice:"info" choice:"warn" choice:"error" choice:"critical"`
|
||||||
Network string `long:"network" ini-name:"network" description:"Decred network to use." choice:"testnet" choice:"mainnet" choice:"simnet"`
|
Network string `long:"network" ini-name:"network" description:"Decred network to use." choice:"testnet" choice:"mainnet" choice:"simnet"`
|
||||||
FeeXPub string `long:"feexpub" ini-name:"feexpub" description:"Cold wallet xpub used for collecting fees."`
|
|
||||||
VSPFee float64 `long:"vspfee" ini-name:"vspfee" description:"Fee percentage charged for VSP use. eg. 2.0 (2%), 0.5 (0.5%)."`
|
VSPFee float64 `long:"vspfee" ini-name:"vspfee" description:"Fee percentage charged for VSP use. eg. 2.0 (2%), 0.5 (0.5%)."`
|
||||||
HomeDir string `long:"homedir" ini-name:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."`
|
|
||||||
ConfigFile string `long:"configfile" ini-name:"configfile" no-ini:"true" description:"Path to configuration file."`
|
|
||||||
DcrdHost string `long:"dcrdhost" ini-name:"dcrdhost" description:"The ip:port to establish a JSON-RPC connection with dcrd. Should be the same host where vspd is running."`
|
DcrdHost string `long:"dcrdhost" ini-name:"dcrdhost" description:"The ip:port to establish a JSON-RPC connection with dcrd. Should be the same host where vspd is running."`
|
||||||
DcrdUser string `long:"dcrduser" ini-name:"dcrduser" description:"Username for dcrd RPC connections."`
|
DcrdUser string `long:"dcrduser" ini-name:"dcrduser" description:"Username for dcrd RPC connections."`
|
||||||
DcrdPass string `long:"dcrdpass" ini-name:"dcrdpass" description:"Password for dcrd RPC connections."`
|
DcrdPass string `long:"dcrdpass" ini-name:"dcrdpass" description:"Password for dcrd RPC connections."`
|
||||||
@ -55,6 +53,11 @@ type config struct {
|
|||||||
BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."`
|
BackupInterval time.Duration `long:"backupinterval" ini-name:"backupinterval" description:"Time period between automatic database backups. Valid time units are {s,m,h}. Minimum 30 seconds."`
|
||||||
VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."`
|
VspClosed bool `long:"vspclosed" ini-name:"vspclosed" description:"Closed prevents the VSP from accepting new tickets."`
|
||||||
|
|
||||||
|
// The following flags should be set on CLI only, not via config file.
|
||||||
|
FeeXPub string `long:"feexpub" no-ini:"true" description:"Cold wallet xpub used for collecting fees. Should be provided once to initialize a vspd database."`
|
||||||
|
HomeDir string `long:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."`
|
||||||
|
ConfigFile string `long:"configfile" no-ini:"true" description:"Path to configuration file."`
|
||||||
|
|
||||||
dbPath string
|
dbPath string
|
||||||
netParams *netParams
|
netParams *netParams
|
||||||
dcrdCert []byte
|
dcrdCert []byte
|
||||||
@ -172,8 +175,7 @@ func loadConfig() (*config, error) {
|
|||||||
_, err := preParser.Parse()
|
_, err := preParser.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if e, ok := err.(*flags.Error); ok && e.Type != flags.ErrHelp {
|
if e, ok := err.(*flags.Error); ok && e.Type != flags.ErrHelp {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
return nil, err
|
||||||
os.Exit(1)
|
|
||||||
} else if ok && e.Type == flags.ErrHelp {
|
} else if ok && e.Type == flags.ErrHelp {
|
||||||
fmt.Fprintln(os.Stdout, err)
|
fmt.Fprintln(os.Stdout, err)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
@ -211,7 +213,6 @@ func loadConfig() (*config, error) {
|
|||||||
// Create a default config file when one does not exist and the user did
|
// Create a default config file when one does not exist and the user did
|
||||||
// not specify an override.
|
// not specify an override.
|
||||||
if preCfg.ConfigFile == defaultConfigFile && !fileExists(preCfg.ConfigFile) {
|
if preCfg.ConfigFile == defaultConfigFile && !fileExists(preCfg.ConfigFile) {
|
||||||
fmt.Printf("Writing a config file with default values to %s\n", defaultConfigFile)
|
|
||||||
preIni := flags.NewIniParser(preParser)
|
preIni := flags.NewIniParser(preParser)
|
||||||
err = preIni.WriteFile(preCfg.ConfigFile,
|
err = preIni.WriteFile(preCfg.ConfigFile,
|
||||||
flags.IniIncludeComments|flags.IniIncludeDefaults)
|
flags.IniIncludeComments|flags.IniIncludeDefaults)
|
||||||
@ -219,6 +220,11 @@ func loadConfig() (*config, error) {
|
|||||||
return nil, fmt.Errorf("error creating a default "+
|
return nil, fmt.Errorf("error creating a default "+
|
||||||
"config file: %v", err)
|
"config file: %v", err)
|
||||||
}
|
}
|
||||||
|
fmt.Printf("Config file with default values written to %s\n", defaultConfigFile)
|
||||||
|
|
||||||
|
// File created, user now has to fill in values. Proceeding with the
|
||||||
|
// default file just causes errors.
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load additional config from file.
|
// Load additional config from file.
|
||||||
@ -226,8 +232,7 @@ func loadConfig() (*config, error) {
|
|||||||
|
|
||||||
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
err = flags.NewIniParser(parser).ParseFile(preCfg.ConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "error parsing config file: %v\n", err)
|
return nil, fmt.Errorf("error parsing config file: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse command line options again to ensure they take precedence.
|
// Parse command line options again to ensure they take precedence.
|
||||||
@ -337,14 +342,36 @@ func loadConfig() (*config, error) {
|
|||||||
// Set the database path
|
// Set the database path
|
||||||
cfg.dbPath = filepath.Join(dataDir, "vspd.db")
|
cfg.dbPath = filepath.Join(dataDir, "vspd.db")
|
||||||
|
|
||||||
// Validate the cold wallet xpub.
|
// If xpub has been provided, create a new database and exit.
|
||||||
if cfg.FeeXPub == "" {
|
if cfg.FeeXPub != "" {
|
||||||
return nil, errors.New("the feexpub option is not set")
|
// If database already exists, return error.
|
||||||
|
if fileExists(cfg.dbPath) {
|
||||||
|
return nil, fmt.Errorf("database already initialized at %s, "+
|
||||||
|
"--feexpub option is not needed.", cfg.dbPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure provided value is a valid key for the selected network.
|
||||||
_, err = hdkeychain.NewKeyFromString(cfg.FeeXPub, cfg.netParams.Params)
|
_, err = hdkeychain.NewKeyFromString(cfg.FeeXPub, cfg.netParams.Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse feexpub: %v", err)
|
return nil, fmt.Errorf("failed to parse feexpub: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create new database.
|
||||||
|
err = database.CreateNew(cfg.dbPath, cfg.FeeXPub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error creating db file %s: %v", cfg.dbPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit with success
|
||||||
|
os.Exit(0)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// If database does not exist, return error.
|
||||||
|
if !fileExists(cfg.dbPath) {
|
||||||
|
return nil, fmt.Errorf("no database exists in %s. Run vspd with the"+
|
||||||
|
" --feexpub option to initialize one.", dataDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,6 +28,8 @@ var (
|
|||||||
ticketBktK = []byte("ticketbkt")
|
ticketBktK = []byte("ticketbkt")
|
||||||
// version is the current database version.
|
// version is the current database version.
|
||||||
versionK = []byte("version")
|
versionK = []byte("version")
|
||||||
|
// feeXPub is the extended public key used for collecting VSP fees.
|
||||||
|
feeXPubK = []byte("feeXPub")
|
||||||
// privatekey is the private key.
|
// privatekey is the private key.
|
||||||
privateKeyK = []byte("privatekey")
|
privateKeyK = []byte("privatekey")
|
||||||
// lastaddressindex is the index of the last address used for fees.
|
// lastaddressindex is the index of the last address used for fees.
|
||||||
@ -63,6 +65,69 @@ func writeBackup(db *bolt.DB, dbFile string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateNew(dbFile, feeXPub string) error {
|
||||||
|
log.Infof("Initializing new database at %s", dbFile)
|
||||||
|
|
||||||
|
db, err := bolt.Open(dbFile, 0600, &bolt.Options{Timeout: 1 * time.Second})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("unable to open db file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Create all storage buckets of the VSP if they don't already exist.
|
||||||
|
err = db.Update(func(tx *bolt.Tx) error {
|
||||||
|
// Create parent bucket.
|
||||||
|
vspBkt, err := tx.CreateBucket(vspBktK)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s bucket: %v", string(vspBktK), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize with database version 1.
|
||||||
|
vbytes := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(vbytes, uint32(1))
|
||||||
|
err = vspBkt.Put(versionK, vbytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Generating ed25519 signing key")
|
||||||
|
|
||||||
|
// Generate ed25519 key
|
||||||
|
_, signKey, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate signing key: %v", err)
|
||||||
|
}
|
||||||
|
err = vspBkt.Put(privateKeyK, signKey.Seed())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Storing extended public key")
|
||||||
|
// Store fee xpub
|
||||||
|
err = vspBkt.Put(feeXPubK, []byte(feeXPub))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create ticket bucket.
|
||||||
|
_, err = vspBkt.CreateBucket(ticketBktK)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create %s bucket: %v", string(ticketBktK), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Database initialized")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Open initializes and returns an open database. If no database file is found
|
// Open initializes and returns an open database. If no database file is found
|
||||||
// at the provided path, a new one will be created.
|
// at the provided path, a new one will be created.
|
||||||
func Open(ctx context.Context, shutdownWg *sync.WaitGroup, dbFile string, backupInterval time.Duration) (*VspDatabase, error) {
|
func Open(ctx context.Context, shutdownWg *sync.WaitGroup, dbFile string, backupInterval time.Duration) (*VspDatabase, error) {
|
||||||
@ -115,48 +180,6 @@ func Open(ctx context.Context, shutdownWg *sync.WaitGroup, dbFile string, backup
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Create all storage buckets of the VSP if they don't already exist.
|
|
||||||
err = db.Update(func(tx *bolt.Tx) error {
|
|
||||||
if tx.Bucket(vspBktK) == nil {
|
|
||||||
log.Debug("Initializing new database")
|
|
||||||
// Create parent bucket.
|
|
||||||
vspBkt, err := tx.CreateBucket(vspBktK)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create %s bucket: %v", string(vspBktK), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize with database version 1.
|
|
||||||
vbytes := make([]byte, 4)
|
|
||||||
binary.LittleEndian.PutUint32(vbytes, uint32(1))
|
|
||||||
err = vspBkt.Put(versionK, vbytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate ed25519 key
|
|
||||||
_, signKey, err := ed25519.GenerateKey(rand.Reader)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to generate signing key: %v", err)
|
|
||||||
}
|
|
||||||
err = vspBkt.Put(privateKeyK, signKey.Seed())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create ticket bucket.
|
|
||||||
_, err = vspBkt.CreateBucket(ticketBktK)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create %s bucket: %v", string(ticketBktK), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &VspDatabase{db: db}, nil
|
return &VspDatabase{db: db}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,3 +210,21 @@ func (vdb *VspDatabase) KeyPair() (ed25519.PrivateKey, ed25519.PublicKey, error)
|
|||||||
|
|
||||||
return signKey, pubKey, err
|
return signKey, pubKey, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (vdb *VspDatabase) GetFeeXPub() (string, error) {
|
||||||
|
var feeXPub string
|
||||||
|
err := vdb.db.View(func(tx *bolt.Tx) error {
|
||||||
|
vspBkt := tx.Bucket(vspBktK)
|
||||||
|
|
||||||
|
xpubBytes := vspBkt.Get(feeXPubK)
|
||||||
|
if xpubBytes == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
feeXPub = string(xpubBytes)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return feeXPub, err
|
||||||
|
}
|
||||||
|
|||||||
@ -34,10 +34,14 @@ func TestDatabase(t *testing.T) {
|
|||||||
var err error
|
var err error
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
ctx, cancel := context.WithCancel(context.TODO())
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
db, err = Open(ctx, &wg, testDb, time.Hour)
|
err = CreateNew(testDb, "feexpub")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error creating test database: %v", err)
|
t.Fatalf("error creating test database: %v", err)
|
||||||
}
|
}
|
||||||
|
db, err = Open(ctx, &wg, testDb, time.Hour)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("error opening test database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Run the sub-test.
|
// Run the sub-test.
|
||||||
t.Run(testName, test)
|
t.Run(testName, test)
|
||||||
|
|||||||
@ -20,17 +20,6 @@ should be used to export an extended public (xpub) key from one of the wallet
|
|||||||
accounts. This xpub key will be provided to vspd via config, and vspd will use
|
accounts. This xpub key will be provided to vspd via config, and vspd will use
|
||||||
it to derive a new addresses for receiving fee payments.
|
it to derive a new addresses for receiving fee payments.
|
||||||
|
|
||||||
## Front-end Server
|
|
||||||
|
|
||||||
The front-end server is where vspd will be running. The port vspd is listening
|
|
||||||
on (default `3000`) should be available for clients to reach over the internet.
|
|
||||||
This port is used for both the API and serving the HTML front end.
|
|
||||||
|
|
||||||
dcrd needs to be running on this server with transaction index enabled
|
|
||||||
(`--txindex`). dcrd is used for fishing ticket details out of the chain, for
|
|
||||||
receiving `blockconnected` notifications, and for broadcasting and checking the
|
|
||||||
status of fee transactions.
|
|
||||||
|
|
||||||
## Voting Servers
|
## Voting Servers
|
||||||
|
|
||||||
A vspd deployment should have a minimum of three remote voting wallets. The
|
A vspd deployment should have a minimum of three remote voting wallets. The
|
||||||
@ -43,6 +32,31 @@ purpose. dcrwallet should be permenantly unlocked and have voting enabled
|
|||||||
(`--enablevoting`). vspd on the front-end server must be able to reach each
|
(`--enablevoting`). vspd on the front-end server must be able to reach each
|
||||||
instance of dcrwallet over RPC.
|
instance of dcrwallet over RPC.
|
||||||
|
|
||||||
|
## Front-end Server
|
||||||
|
|
||||||
|
The front-end server is where vspd will be running. The port vspd is listening
|
||||||
|
on (default `3000`) should be available for clients to reach over the internet.
|
||||||
|
This port is used for both the API, and for serving the HTML front end.
|
||||||
|
|
||||||
|
1. Start an instance of dcrd on this server with transaction index enabled
|
||||||
|
(`--txindex`). dcrd is used for fishing ticket details out of the chain, for
|
||||||
|
receiving `blockconnected` notifications, and for broadcasting and checking
|
||||||
|
the status of fee transactions.
|
||||||
|
|
||||||
|
1. Run `vspd` with no arguments to write a default config file. Modify the
|
||||||
|
config file to set your dcrd and dcrwallet connection details, and any other
|
||||||
|
required customization.
|
||||||
|
|
||||||
|
1. A vspd database must be initialized before vpsd can be started. To do this,
|
||||||
|
provide vspd with the xpub key it should use for collecting fees:
|
||||||
|
|
||||||
|
```no-highlight
|
||||||
|
$ vspd --feexpub=tpubVppjaMjp8GEW...
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Once the database is initialized, vspd can be started for normal operation by
|
||||||
|
running it without the `--feexpub` flag.
|
||||||
|
|
||||||
## Deploying alongside dcrstakepool
|
## Deploying alongside dcrstakepool
|
||||||
|
|
||||||
It is possible to run vspd on the same infrastructure as an existing
|
It is possible to run vspd on the same infrastructure as an existing
|
||||||
@ -75,4 +89,6 @@ database file will also be written to this path when vspd shuts down.
|
|||||||
|
|
||||||
## Disaster Recovery
|
## Disaster Recovery
|
||||||
|
|
||||||
// TODO
|
The database file contains everything needed to restore a vspd deployment -
|
||||||
|
simply place the database file into the vspd data directory and start vspd as
|
||||||
|
normal.
|
||||||
|
|||||||
2
main.go
2
main.go
@ -111,7 +111,7 @@ func run(ctx context.Context) error {
|
|||||||
VspClosed: cfg.VspClosed,
|
VspClosed: cfg.VspClosed,
|
||||||
}
|
}
|
||||||
err = webapi.Start(ctx, shutdownRequestChannel, &shutdownWg, cfg.Listen, db,
|
err = webapi.Start(ctx, shutdownRequestChannel, &shutdownWg, cfg.Listen, db,
|
||||||
dcrd, wallets, cfg.WebServerDebug, cfg.FeeXPub, apiCfg)
|
dcrd, wallets, cfg.WebServerDebug, apiCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to initialize webapi: %v", err)
|
log.Errorf("Failed to initialize webapi: %v", err)
|
||||||
requestShutdown()
|
requestShutdown()
|
||||||
|
|||||||
@ -41,7 +41,7 @@ var signPrivKey ed25519.PrivateKey
|
|||||||
var signPubKey ed25519.PublicKey
|
var signPubKey ed25519.PublicKey
|
||||||
|
|
||||||
func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *sync.WaitGroup,
|
func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *sync.WaitGroup,
|
||||||
listen string, vdb *database.VspDatabase, dConnect rpc.DcrdConnect, wConnect rpc.WalletConnect, debugMode bool, feeXPub string, config Config) error {
|
listen string, vdb *database.VspDatabase, dConnect rpc.DcrdConnect, wConnect rpc.WalletConnect, debugMode bool, config Config) error {
|
||||||
|
|
||||||
cfg = config
|
cfg = config
|
||||||
db = vdb
|
db = vdb
|
||||||
@ -62,12 +62,16 @@ func Start(ctx context.Context, requestShutdownChan chan struct{}, shutdownWg *s
|
|||||||
return fmt.Errorf("could not initialize homepage data: %v", err)
|
return fmt.Errorf("could not initialize homepage data: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the last used address index from the database, and use it to
|
// Get the last used address index and the feeXpub from the database, and
|
||||||
// initialize the address generator.
|
// use them to initialize the address generator.
|
||||||
idx, err := vdb.GetLastAddressIndex()
|
idx, err := vdb.GetLastAddressIndex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("GetLastAddressIndex error: %v", err)
|
return fmt.Errorf("GetLastAddressIndex error: %v", err)
|
||||||
}
|
}
|
||||||
|
feeXPub, err := vdb.GetFeeXPub()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetFeeXPub error: %v", err)
|
||||||
|
}
|
||||||
addrGen, err = newAddressGenerator(feeXPub, config.NetParams, idx)
|
addrGen, err = newAddressGenerator(feeXPub, config.NetParams, idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to initialize fee address generator: %v", err)
|
return fmt.Errorf("failed to initialize fee address generator: %v", err)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user