Import xpub tweaks (#41)
This commit is contained in:
parent
67c8e8f27c
commit
36c748ba12
@ -20,6 +20,9 @@
|
|||||||
stores it in the database. This key is used to sign all API responses, and the
|
stores it in the database. This key is used to sign all API responses, and the
|
||||||
signature is included in the response header `VSP-Signature`. Error responses
|
signature is included in the response header `VSP-Signature`. Error responses
|
||||||
are not signed.
|
are not signed.
|
||||||
|
- An xpub key is provided to dcrvsp via config. The first time dcrvsp starts, it
|
||||||
|
imports this xpub to create a new wallet account. This account is used to
|
||||||
|
derive addresses for fee payments.
|
||||||
- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
|
- VSP API as described in [dcrstakepool #574](https://github.com/decred/dcrstakepool/issues/574)
|
||||||
- Request fee amount (`GET /fee`)
|
- Request fee amount (`GET /fee`)
|
||||||
- Request fee address (`POST /feeaddress`)
|
- Request fee address (`POST /feeaddress`)
|
||||||
|
|||||||
34
config.go
34
config.go
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
@ -31,8 +32,8 @@ 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"`
|
FeeXPub string `long:"feexpub" ini-name:"feexpub" description:"Cold wallet xpub used for collecting fees."`
|
||||||
VSPFee float64 `long:"vspfee" ini-name:"vspfee" description:"The fee percentage charged for VSP use. eg. 0.01 (1%), 0.05 (5%)."`
|
VSPFee float64 `long:"vspfee" ini-name:"vspfee" description:"Fee percentage charged for VSP use. eg. 0.01 (1%), 0.05 (5%)."`
|
||||||
HomeDir string `long:"homedir" ini-name:"homedir" no-ini:"true" description:"Path to application home directory. Used for storing VSP database and logs."`
|
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."`
|
ConfigFile string `long:"configfile" ini-name:"configfile" no-ini:"true" description:"Path to configuration file."`
|
||||||
WalletHost string `long:"wallethost" ini-name:"wallethost" description:"The ip:port to establish a JSON-RPC connection with dcrwallet."`
|
WalletHost string `long:"wallethost" ini-name:"wallethost" description:"The ip:port to establish a JSON-RPC connection with dcrwallet."`
|
||||||
@ -181,11 +182,9 @@ func loadConfig() (*config, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create the home directory if it doesn't already exist.
|
// Create the home directory if it doesn't already exist.
|
||||||
funcName := "loadConfig"
|
|
||||||
err = os.MkdirAll(cfg.HomeDir, 0700)
|
err = os.MkdirAll(cfg.HomeDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "%s: failed to create home directory: %v"
|
err := fmt.Errorf("failed to create home directory: %v", err)
|
||||||
err := fmt.Errorf(str, funcName, err)
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -233,23 +232,17 @@ func loadConfig() (*config, error) {
|
|||||||
|
|
||||||
// Ensure the dcrwallet RPC username is set.
|
// Ensure the dcrwallet RPC username is set.
|
||||||
if cfg.WalletUser == "" {
|
if cfg.WalletUser == "" {
|
||||||
str := "%s: the walletuser option is not set"
|
return nil, errors.New("the walletuser option is not set")
|
||||||
err := fmt.Errorf(str, funcName)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the dcrwallet RPC password is set.
|
// Ensure the dcrwallet RPC password is set.
|
||||||
if cfg.WalletPass == "" {
|
if cfg.WalletPass == "" {
|
||||||
str := "%s: the walletpass option is not set"
|
return nil, errors.New("the walletpass option is not set")
|
||||||
err := fmt.Errorf(str, funcName)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the dcrwallet RPC cert path is set.
|
// Ensure the dcrwallet RPC cert path is set.
|
||||||
if cfg.WalletCert == "" {
|
if cfg.WalletCert == "" {
|
||||||
str := "%s: the walletcert option is not set"
|
return nil, errors.New("the walletcert option is not set")
|
||||||
err := fmt.Errorf(str, funcName)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add default port for the active network if there is no port specified.
|
// Add default port for the active network if there is no port specified.
|
||||||
@ -259,18 +252,14 @@ func loadConfig() (*config, error) {
|
|||||||
cfg.WalletCert = cleanAndExpandPath(cfg.WalletCert)
|
cfg.WalletCert = cleanAndExpandPath(cfg.WalletCert)
|
||||||
cfg.dcrwCert, err = ioutil.ReadFile(cfg.WalletCert)
|
cfg.dcrwCert, err = ioutil.ReadFile(cfg.WalletCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "%s: failed to read dcrwallet cert file: %s"
|
return nil, fmt.Errorf("failed to read dcrwallet cert file: %v", err)
|
||||||
err := fmt.Errorf(str, funcName, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the data directory.
|
// Create the data directory.
|
||||||
dataDir := filepath.Join(cfg.HomeDir, "data", cfg.netParams.Name)
|
dataDir := filepath.Join(cfg.HomeDir, "data", cfg.netParams.Name)
|
||||||
err = os.MkdirAll(dataDir, 0700)
|
err = os.MkdirAll(dataDir, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "%s: failed to create data directory: %v"
|
return nil, fmt.Errorf("failed to create data directory: %v", err)
|
||||||
err := fmt.Errorf(str, funcName, err)
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize loggers and log rotation.
|
// Initialize loggers and log rotation.
|
||||||
@ -282,9 +271,12 @@ func loadConfig() (*config, error) {
|
|||||||
cfg.dbPath = filepath.Join(dataDir, "vsp.db")
|
cfg.dbPath = filepath.Join(dataDir, "vsp.db")
|
||||||
|
|
||||||
// Validate the cold wallet xpub.
|
// Validate the cold wallet xpub.
|
||||||
|
if cfg.FeeXPub == "" {
|
||||||
|
return nil, errors.New("the feexpub option is not set")
|
||||||
|
}
|
||||||
_, err = hdkeychain.NewKeyFromString(cfg.FeeXPub, cfg.netParams.Params)
|
_, err = hdkeychain.NewKeyFromString(cfg.FeeXPub, cfg.netParams.Params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to parse feexpub: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
|
|||||||
66
main.go
66
main.go
@ -10,6 +10,11 @@ import (
|
|||||||
"github.com/jholdstock/dcrvsp/database"
|
"github.com/jholdstock/dcrvsp/database"
|
||||||
"github.com/jholdstock/dcrvsp/rpc"
|
"github.com/jholdstock/dcrvsp/rpc"
|
||||||
"github.com/jholdstock/dcrvsp/webapi"
|
"github.com/jholdstock/dcrvsp/webapi"
|
||||||
|
"github.com/jrick/wsrpc/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
feeAccountName = "fees"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -69,41 +74,22 @@ func run(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the masterpubkey from the fees account, if it exists, and make
|
// Ensure the wallet account for collecting fees exists and matches config.
|
||||||
// sure it matches the configuration.
|
err = setupFeeAccount(ctx, walletClient, cfg.FeeXPub)
|
||||||
var existingXPub string
|
|
||||||
err = walletClient.Call(ctx, "getmasterpubkey", &existingXPub, "fees")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// TODO - ignore account not found
|
log.Errorf("Fee account error: %v", err)
|
||||||
log.Errorf("dcrwallet RPC error: %v", err)
|
|
||||||
requestShutdown()
|
requestShutdown()
|
||||||
shutdownWg.Wait()
|
shutdownWg.Wait()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// account exists - make sure it matches the configuration.
|
|
||||||
if existingXPub != cfg.FeeXPub {
|
|
||||||
log.Errorf("fees account xpub differs: %s != %s", existingXPub, cfg.FeeXPub)
|
|
||||||
requestShutdown()
|
|
||||||
shutdownWg.Wait()
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
// account does not exist - import xpub from configuration.
|
|
||||||
if err = walletClient.Call(ctx, "importxpub", nil, "fees"); err != nil {
|
|
||||||
log.Errorf("failed to import xpub: %v", err)
|
|
||||||
requestShutdown()
|
|
||||||
shutdownWg.Wait()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and start webapi server.
|
// Create and start webapi server.
|
||||||
apiCfg := webapi.Config{
|
apiCfg := webapi.Config{
|
||||||
SignKey: signKey,
|
SignKey: signKey,
|
||||||
PubKey: pubKey,
|
PubKey: pubKey,
|
||||||
VSPFee: cfg.VSPFee,
|
VSPFee: cfg.VSPFee,
|
||||||
NetParams: cfg.netParams.Params,
|
NetParams: cfg.netParams.Params,
|
||||||
|
FeeAccountName: feeAccountName,
|
||||||
}
|
}
|
||||||
// TODO: Make releaseMode properly configurable. Release mode enables very
|
// TODO: Make releaseMode properly configurable. Release mode enables very
|
||||||
// detailed webserver logging and live reloading of HTML templates.
|
// detailed webserver logging and live reloading of HTML templates.
|
||||||
@ -121,3 +107,37 @@ func run(ctx context.Context) error {
|
|||||||
|
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupFeeAccount(ctx context.Context, walletClient *wsrpc.Client, feeXpub string) error {
|
||||||
|
// Check if account for fee collection already exists.
|
||||||
|
var accounts map[string]float64
|
||||||
|
err := walletClient.Call(ctx, "listaccounts", &accounts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dcrwallet RPC error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := accounts[feeAccountName]; ok {
|
||||||
|
// Account already exists. Check xpub matches xpub from config.
|
||||||
|
var existingXPub string
|
||||||
|
err = walletClient.Call(ctx, "getmasterpubkey", &existingXPub, feeAccountName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dcrwallet RPC error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if existingXPub != feeXpub {
|
||||||
|
return fmt.Errorf("existing account xpub differs from config: %s != %s", existingXPub, feeXpub)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Using existing wallet account %q to collect fees", feeAccountName)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Account does not exist. Create it using xpub from config.
|
||||||
|
if err = walletClient.Call(ctx, "importxpub", nil, feeAccountName, feeXpub); err != nil {
|
||||||
|
log.Errorf("Failed to import xpub: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Debugf("Created new wallet account %q to collect fees", feeAccountName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -205,7 +205,7 @@ func feeAddress(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newAddress string
|
var newAddress string
|
||||||
err = walletClient.Call(ctx, "getnewaddress", &newAddress, "fees")
|
err = walletClient.Call(ctx, "getnewaddress", &newAddress, cfg.FeeAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("GetNewAddress error: %v", err)
|
log.Errorf("GetNewAddress error: %v", err)
|
||||||
sendErrorResponse("unable to generate fee address", http.StatusInternalServerError, c)
|
sendErrorResponse("unable to generate fee address", http.StatusInternalServerError, c)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ type Config struct {
|
|||||||
PubKey ed25519.PublicKey
|
PubKey ed25519.PublicKey
|
||||||
VSPFee float64
|
VSPFee float64
|
||||||
NetParams *chaincfg.Params
|
NetParams *chaincfg.Params
|
||||||
|
FeeAccountName string
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg Config
|
var cfg Config
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user