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

View File

@ -45,7 +45,7 @@ type config struct {
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."`
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."`
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."`
@ -155,7 +155,7 @@ func loadConfig() (*config, error) {
HomeDir: defaultHomeDir,
ConfigFile: defaultConfigFile,
DcrdHost: defaultDcrdHost,
WalletHost: defaultWalletHost,
WalletHosts: []string{defaultWalletHost},
WebServerDebug: defaultWebServerDebug,
BackupInterval: defaultBackupInterval,
VspClosed: defaultVspClosed,
@ -239,11 +239,13 @@ func loadConfig() (*config, error) {
}
// Set the active network.
minRequired := 1
switch cfg.Network {
case "testnet":
cfg.netParams = &testNet3Params
case "mainnet":
cfg.netParams = &mainNetParams
minRequired = 3
case "simnet":
cfg.netParams = &simNetParams
}
@ -302,9 +304,17 @@ func loadConfig() (*config, error) {
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.
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.WalletHost = normalizeAddress(cfg.WalletHost, cfg.netParams.WalletRPCServerPort)
// Create the data directory.
dataDir := filepath.Join(cfg.HomeDir, "data", cfg.netParams.Name)

34
main.go
View File

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

View File

@ -11,6 +11,9 @@ import (
// Caller provides a client interface to perform JSON-RPC remote procedure calls.
type Caller interface {
// String returns the dialed URL.
String() string
// Call performs the remote procedure call defined by method and
// waits for a response or a broken client connection.
// 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
err := c.Call(ctx, "version", &verMap)
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"]
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 {
return nil, fmt.Errorf("wrong dcrwallet RPC version: got %s, expected %s",
walletVersion.VersionString, requiredWalletVersion)
return nil, fmt.Errorf("dcrwallet '%s' has wrong RPC version: got %s, expected %s",
c.String(), walletVersion.VersionString, requiredWalletVersion)
}
// Verify dcrwallet is voting, unlocked, and is connected to dcrd (not SPV).
var walletInfo wallettypes.WalletInfoResult
err = c.Call(ctx, "walletinfo", &walletInfo)
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 {
return nil, fmt.Errorf("wallet has voting disabled")
return nil, fmt.Errorf("wallet '%s' has voting disabled", c.String())
}
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 {
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.
var netID wire.CurrencyNet
err = c.Call(ctx, "getcurrentnet", &netID)
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 {
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

View File

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

View File

@ -17,7 +17,7 @@ func setVoteChoices(c *gin.Context) {
rawRequest := c.MustGet("RawRequest").([]byte)
ticket := c.MustGet("Ticket").(database.Ticket)
knownTicket := c.MustGet("KnownTicket").(bool)
walletClient := c.MustGet("WalletClient").(*rpc.WalletRPC)
walletClients := c.MustGet("WalletClient").([]*rpc.WalletRPC)
if !knownTicket {
log.Warnf("Invalid ticket from %s", c.ClientIP())
@ -56,11 +56,13 @@ func setVoteChoices(c *gin.Context) {
// wallets if their fee is confirmed.
if ticket.FeeConfirmed {
for agenda, choice := range voteChoices {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil {
log.Errorf("SetVoteChoice failed: %v", err)
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
return
for _, walletClient := range walletClients {
err = walletClient.SetVoteChoice(agenda, choice, ticket.Hash)
if err != nil {
log.Errorf("SetVoteChoice failed: %v", err)
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
return
}
}
}
}

View File

@ -38,13 +38,13 @@ const (
var cfg Config
var db *database.VspDatabase
var dcrdConnect rpc.Connect
var walletConnect rpc.Connect
var walletConnect []rpc.Connect
var addrGen *addressGenerator
var signPrivKey ed25519.PrivateKey
var signPubKey ed25519.PublicKey
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
db = vdb