Import xpub tweaks (#41)

This commit is contained in:
Jamie Holdstock 2020-05-20 15:18:24 +01:00 committed by GitHub
parent 67c8e8f27c
commit 36c748ba12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 54 deletions

View File

@ -20,6 +20,9 @@
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
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)
- Request fee amount (`GET /fee`)
- Request fee address (`POST /feeaddress`)

View File

@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"io/ioutil"
"net"
@ -31,8 +32,8 @@ type config struct {
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"`
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:"The fee percentage charged for VSP use. eg. 0.01 (1%), 0.05 (5%)."`
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. 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."`
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."`
@ -181,11 +182,9 @@ func loadConfig() (*config, error) {
}
// Create the home directory if it doesn't already exist.
funcName := "loadConfig"
err = os.MkdirAll(cfg.HomeDir, 0700)
if err != nil {
str := "%s: failed to create home directory: %v"
err := fmt.Errorf(str, funcName, err)
err := fmt.Errorf("failed to create home directory: %v", err)
fmt.Fprintln(os.Stderr, err)
return nil, err
}
@ -233,23 +232,17 @@ func loadConfig() (*config, error) {
// Ensure the dcrwallet RPC username is set.
if cfg.WalletUser == "" {
str := "%s: the walletuser option is not set"
err := fmt.Errorf(str, funcName)
return nil, err
return nil, errors.New("the walletuser option is not set")
}
// Ensure the dcrwallet RPC password is set.
if cfg.WalletPass == "" {
str := "%s: the walletpass option is not set"
err := fmt.Errorf(str, funcName)
return nil, err
return nil, errors.New("the walletpass option is not set")
}
// Ensure the dcrwallet RPC cert path is set.
if cfg.WalletCert == "" {
str := "%s: the walletcert option is not set"
err := fmt.Errorf(str, funcName)
return nil, err
return nil, errors.New("the walletcert option is not set")
}
// 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.dcrwCert, err = ioutil.ReadFile(cfg.WalletCert)
if err != nil {
str := "%s: failed to read dcrwallet cert file: %s"
err := fmt.Errorf(str, funcName, err)
return nil, err
return nil, fmt.Errorf("failed to read dcrwallet cert file: %v", err)
}
// Create the data directory.
dataDir := filepath.Join(cfg.HomeDir, "data", cfg.netParams.Name)
err = os.MkdirAll(dataDir, 0700)
if err != nil {
str := "%s: failed to create data directory: %v"
err := fmt.Errorf(str, funcName, err)
return nil, err
return nil, fmt.Errorf("failed to create data directory: %v", err)
}
// Initialize loggers and log rotation.
@ -282,9 +271,12 @@ func loadConfig() (*config, error) {
cfg.dbPath = filepath.Join(dataDir, "vsp.db")
// 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)
if err != nil {
return nil, err
return nil, fmt.Errorf("failed to parse feexpub: %v", err)
}
return &cfg, nil

76
main.go
View File

@ -10,6 +10,11 @@ import (
"github.com/jholdstock/dcrvsp/database"
"github.com/jholdstock/dcrvsp/rpc"
"github.com/jholdstock/dcrvsp/webapi"
"github.com/jrick/wsrpc/v2"
)
const (
feeAccountName = "fees"
)
func main() {
@ -68,42 +73,23 @@ func run(ctx context.Context) error {
shutdownWg.Wait()
return err
}
// Get the masterpubkey from the fees account, if it exists, and make
// sure it matches the configuration.
var existingXPub string
err = walletClient.Call(ctx, "getmasterpubkey", &existingXPub, "fees")
// Ensure the wallet account for collecting fees exists and matches config.
err = setupFeeAccount(ctx, walletClient, cfg.FeeXPub)
if err != nil {
// TODO - ignore account not found
log.Errorf("dcrwallet RPC error: %v", err)
log.Errorf("Fee account error: %v", err)
requestShutdown()
shutdownWg.Wait()
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.
apiCfg := webapi.Config{
SignKey: signKey,
PubKey: pubKey,
VSPFee: cfg.VSPFee,
NetParams: cfg.netParams.Params,
SignKey: signKey,
PubKey: pubKey,
VSPFee: cfg.VSPFee,
NetParams: cfg.netParams.Params,
FeeAccountName: feeAccountName,
}
// TODO: Make releaseMode properly configurable. Release mode enables very
// detailed webserver logging and live reloading of HTML templates.
@ -121,3 +107,37 @@ func run(ctx context.Context) error {
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
}

View File

@ -205,7 +205,7 @@ func feeAddress(c *gin.Context) {
}
var newAddress string
err = walletClient.Call(ctx, "getnewaddress", &newAddress, "fees")
err = walletClient.Call(ctx, "getnewaddress", &newAddress, cfg.FeeAccountName)
if err != nil {
log.Errorf("GetNewAddress error: %v", err)
sendErrorResponse("unable to generate fee address", http.StatusInternalServerError, c)

View File

@ -16,10 +16,11 @@ import (
)
type Config struct {
SignKey ed25519.PrivateKey
PubKey ed25519.PublicKey
VSPFee float64
NetParams *chaincfg.Params
SignKey ed25519.PrivateKey
PubKey ed25519.PublicKey
VSPFee float64
NetParams *chaincfg.Params
FeeAccountName string
}
var cfg Config