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
|
||||
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`)
|
||||
|
||||
34
config.go
34
config.go
@ -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
|
||||
|
||||
74
main.go
74
main.go
@ -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() {
|
||||
@ -69,41 +74,22 @@ func run(ctx context.Context) error {
|
||||
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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user