multi wallet support (#32)

* multiwallet

* print host

Co-authored-by: Jamie Holdstock <jholdstock@decred.org>
This commit is contained in:
jholdstock 2020-05-28 12:52:06 +01:00
parent 6a100811f4
commit 225dcaf29e
8 changed files with 112 additions and 77 deletions

View File

@ -14,7 +14,7 @@ import (
type NotificationHandler struct { type NotificationHandler struct {
Ctx context.Context Ctx context.Context
Db *database.VspDatabase Db *database.VspDatabase
WalletConnect rpc.Connect WalletConnect []rpc.Connect
NetParams *chaincfg.Params NetParams *chaincfg.Params
closed chan struct{} closed chan struct{}
dcrdClient *rpc.DcrdRPC dcrdClient *rpc.DcrdRPC
@ -108,19 +108,22 @@ func (n *NotificationHandler) Notify(method string, params json.RawMessage) erro
return nil return nil
} }
var walletClient *rpc.WalletRPC walletClients := make([]*rpc.WalletRPC, len(n.WalletConnect))
walletConn, err := n.WalletConnect() for i := 0; i < len(n.WalletConnect); i++ {
walletConn, err := n.WalletConnect[i]()
if err != nil { if err != nil {
// TODO: what host?
log.Errorf("dcrwallet connection error: %v", err) log.Errorf("dcrwallet connection error: %v", err)
// If this fails, there is nothing more we can do. Return. // If this fails, there is nothing more we can do. Return.
return nil return nil
} }
walletClient, err = rpc.WalletClient(n.Ctx, walletConn, n.NetParams) walletClients[i], err = rpc.WalletClient(n.Ctx, walletConn, n.NetParams)
if err != nil { if err != nil {
log.Errorf("dcrwallet client error: %v", err) log.Errorf("dcrwallet '%s' client error: %v", walletConn.String(), err)
// If this fails, there is nothing more we can do. Return. // If this fails, there is nothing more we can do. Return.
return nil return nil
} }
}
for _, ticket := range unconfirmedFees { for _, ticket := range unconfirmedFees {
feeTx, err := n.dcrdClient.GetRawTransaction(ticket.FeeTxHash) feeTx, err := n.dcrdClient.GetRawTransaction(ticket.FeeTxHash)
@ -147,14 +150,17 @@ func (n *NotificationHandler) Notify(method string, params json.RawMessage) erro
log.Errorf("GetRawTransaction error: %v", err) log.Errorf("GetRawTransaction error: %v", err)
continue continue
} }
for _, walletClient := range walletClients {
err = walletClient.AddTransaction(rawTicket.BlockHash, rawTicket.Hex) err = walletClient.AddTransaction(rawTicket.BlockHash, rawTicket.Hex)
if err != nil { if err != nil {
log.Errorf("AddTransaction error: %v", err) log.Errorf("AddTransaction error on dcrwallet '%s': %v",
walletClient.String(), err)
continue continue
} }
err = walletClient.ImportPrivKey(ticket.VotingWIF) err = walletClient.ImportPrivKey(ticket.VotingWIF)
if err != nil { if err != nil {
log.Errorf("ImportPrivKey error: %v", err) log.Errorf("ImportPrivKey error on dcrwallet '%s': %v",
walletClient.String(), err)
continue continue
} }
@ -162,11 +168,14 @@ func (n *NotificationHandler) Notify(method string, params json.RawMessage) erro
for agenda, choice := range ticket.VoteChoices { for agenda, choice := range ticket.VoteChoices {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash) err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil { if err != nil {
log.Errorf("SetVoteChoice error: %v", err) log.Errorf("SetVoteChoice error on dcrwallet '%s': %v",
walletClient.String(), err)
continue continue
} }
} }
log.Debugf("Ticket added to voting wallet: ticketHash=%s", ticket.Hash) log.Debugf("Ticket added to voting wallet '%s': ticketHash=%s",
walletClient.String(), ticket.Hash)
}
} }
} }

View File

@ -45,7 +45,7 @@ type config struct {
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."`
DcrdCert string `long:"dcrdcert" ini-name:"dcrdcert" description:"The dcrd RPC certificate file."` DcrdCert string `long:"dcrdcert" ini-name:"dcrdcert" description:"The dcrd RPC certificate file."`
WalletHost string `long:"wallethost" ini-name:"wallethost" description:"The ip:port to establish a JSON-RPC connection with voting dcrwallet."` WalletHosts []string `long:"wallethost" ini-name:"wallethost" description:"Add an ip:port to establish a JSON-RPC connection with voting dcrwallet."`
WalletUser string `long:"walletuser" ini-name:"walletuser" description:"Username for dcrwallet RPC connections."` WalletUser string `long:"walletuser" ini-name:"walletuser" description:"Username for dcrwallet RPC connections."`
WalletPass string `long:"walletpass" ini-name:"walletpass" description:"Password for dcrwallet RPC connections."` WalletPass string `long:"walletpass" ini-name:"walletpass" description:"Password for dcrwallet RPC connections."`
WalletCert string `long:"walletcert" ini-name:"walletcert" description:"The dcrwallet RPC certificate file."` WalletCert string `long:"walletcert" ini-name:"walletcert" description:"The dcrwallet RPC certificate file."`
@ -155,7 +155,7 @@ func loadConfig() (*config, error) {
HomeDir: defaultHomeDir, HomeDir: defaultHomeDir,
ConfigFile: defaultConfigFile, ConfigFile: defaultConfigFile,
DcrdHost: defaultDcrdHost, DcrdHost: defaultDcrdHost,
WalletHost: defaultWalletHost, WalletHosts: []string{defaultWalletHost},
WebServerDebug: defaultWebServerDebug, WebServerDebug: defaultWebServerDebug,
BackupInterval: defaultBackupInterval, BackupInterval: defaultBackupInterval,
VspClosed: defaultVspClosed, VspClosed: defaultVspClosed,
@ -239,11 +239,13 @@ func loadConfig() (*config, error) {
} }
// Set the active network. // Set the active network.
minRequired := 1
switch cfg.Network { switch cfg.Network {
case "testnet": case "testnet":
cfg.netParams = &testNet3Params cfg.netParams = &testNet3Params
case "mainnet": case "mainnet":
cfg.netParams = &mainNetParams cfg.netParams = &mainNetParams
minRequired = 3
case "simnet": case "simnet":
cfg.netParams = &simNetParams cfg.netParams = &simNetParams
} }
@ -302,9 +304,17 @@ func loadConfig() (*config, error) {
return nil, fmt.Errorf("failed to read dcrwallet cert file: %v", err) return nil, fmt.Errorf("failed to read dcrwallet cert file: %v", err)
} }
// Verify minimum number of voting wallets are configured.
if minRequired < len(cfg.WalletHosts) {
return nil, fmt.Errorf("minimum required voting wallets has not been met: %d < %d",
len(cfg.WalletHosts), minRequired)
}
// 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.
for i := 0; i < len(cfg.WalletHosts); i++ {
cfg.WalletHosts[i] = normalizeAddress(cfg.WalletHosts[i], cfg.netParams.WalletRPCServerPort)
}
cfg.DcrdHost = normalizeAddress(cfg.DcrdHost, cfg.netParams.DcrdRPCServerPort) cfg.DcrdHost = normalizeAddress(cfg.DcrdHost, cfg.netParams.DcrdRPCServerPort)
cfg.WalletHost = normalizeAddress(cfg.WalletHost, cfg.netParams.WalletRPCServerPort)
// 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)

16
main.go
View File

@ -84,22 +84,26 @@ func run(ctx context.Context) error {
// Create RPC client for remote dcrwallet instance (used for voting). // Create RPC client for remote dcrwallet instance (used for voting).
// Dial once just to validate config. // Dial once just to validate config.
walletConnect := rpc.Setup(ctx, &shutdownWg, cfg.WalletUser, cfg.WalletPass, walletConnect := make([]rpc.Connect, len(cfg.WalletHosts))
cfg.WalletHost, cfg.walletCert, nil) walletConn := make([]rpc.Caller, len(cfg.WalletHosts))
walletConn, err := walletConnect() for i := 0; i < len(cfg.WalletHosts); i++ {
walletConnect[i] = rpc.Setup(ctx, &shutdownWg, cfg.WalletUser, cfg.WalletPass,
cfg.WalletHosts[i], cfg.walletCert, nil)
walletConn[i], err = walletConnect[i]()
if err != nil { if err != nil {
log.Errorf("dcrwallet connection error: %v", err) log.Errorf("dcrwallet '%s' connection error: %v", cfg.WalletHosts[i], err)
requestShutdown() requestShutdown()
shutdownWg.Wait() shutdownWg.Wait()
return err return err
} }
_, err = rpc.WalletClient(ctx, walletConn, cfg.netParams.Params) _, err = rpc.WalletClient(ctx, walletConn[i], cfg.netParams.Params)
if err != nil { if err != nil {
log.Errorf("dcrwallet client error: %v", err) log.Errorf("dcrwallet '%s' client error: %v", cfg.WalletHosts[i], err)
requestShutdown() requestShutdown()
shutdownWg.Wait() shutdownWg.Wait()
return err return err
} }
}
// Create a dcrd client with an attached notification handler which will run // Create a dcrd client with an attached notification handler which will run
// in the background. // in the background.

View File

@ -11,6 +11,9 @@ import (
// Caller provides a client interface to perform JSON-RPC remote procedure calls. // Caller provides a client interface to perform JSON-RPC remote procedure calls.
type Caller interface { type Caller interface {
// String returns the dialed URL.
String() string
// Call performs the remote procedure call defined by method and // Call performs the remote procedure call defined by method and
// waits for a response or a broken client connection. // waits for a response or a broken client connection.
// Args provides positional parameters for the call. // Args provides positional parameters for the call.

View File

@ -28,41 +28,46 @@ func WalletClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*W
var verMap map[string]dcrdtypes.VersionResult var verMap map[string]dcrdtypes.VersionResult
err := c.Call(ctx, "version", &verMap) err := c.Call(ctx, "version", &verMap)
if err != nil { if err != nil {
return nil, fmt.Errorf("version check failed: %v", err) return nil, fmt.Errorf("version check on dcrwallet '%s' failed: %v",
c.String(), err)
} }
walletVersion, exists := verMap["dcrwalletjsonrpcapi"] walletVersion, exists := verMap["dcrwalletjsonrpcapi"]
if !exists { if !exists {
return nil, fmt.Errorf("version response missing 'dcrwalletjsonrpcapi'") return nil, fmt.Errorf("version response on dcrwallet '%s' missing 'dcrwalletjsonrpcapi'",
c.String())
} }
if walletVersion.VersionString != requiredWalletVersion { if walletVersion.VersionString != requiredWalletVersion {
return nil, fmt.Errorf("wrong dcrwallet RPC version: got %s, expected %s", return nil, fmt.Errorf("dcrwallet '%s' has wrong RPC version: got %s, expected %s",
walletVersion.VersionString, requiredWalletVersion) c.String(), walletVersion.VersionString, requiredWalletVersion)
} }
// Verify dcrwallet is voting, unlocked, and is connected to dcrd (not SPV). // Verify dcrwallet is voting, unlocked, and is connected to dcrd (not SPV).
var walletInfo wallettypes.WalletInfoResult var walletInfo wallettypes.WalletInfoResult
err = c.Call(ctx, "walletinfo", &walletInfo) err = c.Call(ctx, "walletinfo", &walletInfo)
if err != nil { if err != nil {
return nil, fmt.Errorf("walletinfo check failed: %v", err) return nil, fmt.Errorf("walletinfo check on dcrwallet '%s' failed: %v",
c.String(), err)
} }
if !walletInfo.Voting { if !walletInfo.Voting {
return nil, fmt.Errorf("wallet has voting disabled") return nil, fmt.Errorf("wallet '%s' has voting disabled", c.String())
} }
if !walletInfo.Unlocked { if !walletInfo.Unlocked {
return nil, fmt.Errorf("wallet is not unlocked") return nil, fmt.Errorf("wallet '%s' is not unlocked", c.String())
} }
if !walletInfo.DaemonConnected { if !walletInfo.DaemonConnected {
return nil, fmt.Errorf("wallet is not connected to dcrd") return nil, fmt.Errorf("wallet '%s' is not connected to dcrd", c.String())
} }
// Verify dcrwallet is on the correct network. // Verify dcrwallet is on the correct network.
var netID wire.CurrencyNet var netID wire.CurrencyNet
err = c.Call(ctx, "getcurrentnet", &netID) err = c.Call(ctx, "getcurrentnet", &netID)
if err != nil { if err != nil {
return nil, fmt.Errorf("getcurrentnet check failed: %v", err) return nil, fmt.Errorf("getcurrentnet check on dcrwallet '%s' failed: %v",
c.String(), err)
} }
if netID != netParams.Net { if netID != netParams.Net {
return nil, fmt.Errorf("dcrwallet running on %s, expected %s", netID, netParams.Net) return nil, fmt.Errorf("dcrwallet '%s' running on %s, expected %s",
c.String(), netID, netParams.Net)
} }
return &WalletRPC{c, ctx}, nil return &WalletRPC{c, ctx}, nil

View File

@ -37,19 +37,21 @@ func withDcrdClient() gin.HandlerFunc {
// context for downstream handlers to make use of. // context for downstream handlers to make use of.
func withWalletClient() gin.HandlerFunc { func withWalletClient() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
walletConn, err := walletConnect() walletClient := make([]*rpc.WalletRPC, len(walletConnect))
for i := 0; i < len(walletConnect); i++ {
walletConn, err := walletConnect[i]()
if err != nil { if err != nil {
log.Errorf("dcrwallet connection error: %v", err) log.Errorf("dcrwallet '%s' connection error: %v", err)
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c) sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
return return
} }
walletClient, err := rpc.WalletClient(c, walletConn, cfg.NetParams) walletClient[i], err = rpc.WalletClient(c, walletConn, cfg.NetParams)
if err != nil { if err != nil {
log.Errorf("dcrwallet client error: %v", err) log.Errorf("dcrwallet '%s' client error: %v", walletClient[i].String(), err)
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c) sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
return return
} }
}
c.Set("WalletClient", walletClient) c.Set("WalletClient", walletClient)
} }
} }

View File

@ -17,7 +17,7 @@ func setVoteChoices(c *gin.Context) {
rawRequest := c.MustGet("RawRequest").([]byte) rawRequest := c.MustGet("RawRequest").([]byte)
ticket := c.MustGet("Ticket").(database.Ticket) ticket := c.MustGet("Ticket").(database.Ticket)
knownTicket := c.MustGet("KnownTicket").(bool) knownTicket := c.MustGet("KnownTicket").(bool)
walletClient := c.MustGet("WalletClient").(*rpc.WalletRPC) walletClients := c.MustGet("WalletClient").([]*rpc.WalletRPC)
if !knownTicket { if !knownTicket {
log.Warnf("Invalid ticket from %s", c.ClientIP()) log.Warnf("Invalid ticket from %s", c.ClientIP())
@ -56,6 +56,7 @@ func setVoteChoices(c *gin.Context) {
// wallets if their fee is confirmed. // wallets if their fee is confirmed.
if ticket.FeeConfirmed { if ticket.FeeConfirmed {
for agenda, choice := range voteChoices { for agenda, choice := range voteChoices {
for _, walletClient := range walletClients {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash) err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil { if err != nil {
log.Errorf("SetVoteChoice failed: %v", err) log.Errorf("SetVoteChoice failed: %v", err)
@ -64,6 +65,7 @@ func setVoteChoices(c *gin.Context) {
} }
} }
} }
}
log.Debugf("Vote choices updated for ticket: ticketHash=%s", ticket.Hash) log.Debugf("Vote choices updated for ticket: ticketHash=%s", ticket.Hash)

View File

@ -38,13 +38,13 @@ const (
var cfg Config var cfg Config
var db *database.VspDatabase var db *database.VspDatabase
var dcrdConnect rpc.Connect var dcrdConnect rpc.Connect
var walletConnect rpc.Connect var walletConnect []rpc.Connect
var addrGen *addressGenerator var addrGen *addressGenerator
var signPrivKey ed25519.PrivateKey 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.Connect, wConnect rpc.Connect, debugMode bool, feeXPub string, config Config) error { listen string, vdb *database.VspDatabase, dConnect rpc.Connect, wConnect []rpc.Connect, debugMode bool, feeXPub string, config Config) error {
cfg = config cfg = config
db = vdb db = vdb