Simplify dcrd and dcrwallet client creation.

This commit is contained in:
jholdstock 2020-06-02 13:51:53 +01:00 committed by David Hill
parent fb6ea54f15
commit 7da79c7561
8 changed files with 173 additions and 156 deletions

View File

@ -14,7 +14,7 @@ import (
type NotificationHandler struct {
Ctx context.Context
Db *database.VspDatabase
WalletConnect []rpc.Connect
Wallets rpc.WalletConnect
NetParams *chaincfg.Params
closed chan struct{}
dcrdClient *rpc.DcrdRPC
@ -108,21 +108,12 @@ func (n *NotificationHandler) Notify(method string, params json.RawMessage) erro
return nil
}
walletClients := make([]*rpc.WalletRPC, len(n.WalletConnect))
for i := 0; i < len(n.WalletConnect); i++ {
walletConn, err := n.WalletConnect[i]()
walletClients, err := n.Wallets.Clients(n.Ctx, n.NetParams)
if err != nil {
log.Errorf("dcrwallet connection error: %v", err)
log.Error(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 client error: %v", err)
// If this fails, there is nothing more we can do. Return.
return nil
}
}
for _, ticket := range unconfirmedFees {
feeTx, err := n.dcrdClient.GetRawTransaction(ticket.FeeTxHash)
@ -186,12 +177,9 @@ func (n *NotificationHandler) Close() error {
return nil
}
func (n *NotificationHandler) connect(dcrdConnect rpc.Connect) error {
dcrdConn, err := dcrdConnect()
if err != nil {
return err
}
n.dcrdClient, err = rpc.DcrdClient(n.Ctx, dcrdConn, n.NetParams)
func (n *NotificationHandler) connect(dcrdConnect rpc.DcrdConnect) error {
var err error
n.dcrdClient, err = dcrdConnect.Client(n.Ctx, n.NetParams)
if err != nil {
return err
}
@ -213,7 +201,7 @@ func (n *NotificationHandler) connect(dcrdConnect rpc.Connect) error {
}
}
func Start(n *NotificationHandler, dcrdConnect rpc.Connect) {
func Start(n *NotificationHandler, dcrdConnect rpc.DcrdConnect) {
// Loop forever attempting to create a connection to the dcrd server.
go func() {

40
main.go
View File

@ -64,56 +64,38 @@ func run(ctx context.Context) error {
// Create RPC client for local dcrd instance (used for broadcasting and
// checking the status of fee transactions).
// Dial once just to validate config.
dcrdConnect := rpc.Setup(ctx, &shutdownWg, cfg.DcrdUser, cfg.DcrdPass,
dcrd := rpc.SetupDcrd(ctx, &shutdownWg, cfg.DcrdUser, cfg.DcrdPass,
cfg.DcrdHost, cfg.dcrdCert, nil)
dcrdConn, err := dcrdConnect()
// Dial once just to validate config.
_, err = dcrd.Client(ctx, cfg.netParams.Params)
if err != nil {
log.Errorf("dcrd connection error: %v", err)
requestShutdown()
shutdownWg.Wait()
return err
}
_, err = rpc.DcrdClient(ctx, dcrdConn, cfg.netParams.Params)
if err != nil {
log.Errorf("dcrd client error: %v", err)
log.Error(err)
requestShutdown()
shutdownWg.Wait()
return err
}
// Create RPC client for remote dcrwallet instance (used for voting).
wallets := rpc.SetupWallet(ctx, &shutdownWg, cfg.WalletUser, cfg.WalletPass,
cfg.WalletHosts, cfg.walletCert)
// Dial once just to validate config.
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]()
_, err = wallets.Clients(ctx, cfg.netParams.Params)
if err != nil {
log.Errorf("dcrwallet connection error: %v", err)
log.Error(err)
requestShutdown()
shutdownWg.Wait()
return err
}
_, err = rpc.WalletClient(ctx, walletConn[i], cfg.netParams.Params)
if err != nil {
log.Errorf("dcrwallet client error: %v", err)
requestShutdown()
shutdownWg.Wait()
return err
}
}
// Create a dcrd client with an attached notification handler which will run
// in the background.
notifHandler := &background.NotificationHandler{
Ctx: ctx,
Db: db,
WalletConnect: walletConnect,
Wallets: wallets,
NetParams: cfg.netParams.Params,
}
dcrdWithNotifHandler := rpc.Setup(ctx, &shutdownWg, cfg.DcrdUser, cfg.DcrdPass,
dcrdWithNotifHandler := rpc.SetupDcrd(ctx, &shutdownWg, cfg.DcrdUser, cfg.DcrdPass,
cfg.DcrdHost, cfg.dcrdCert, notifHandler)
// Start background process which will continually attempt to reconnect to
@ -129,7 +111,7 @@ func run(ctx context.Context) error {
VspClosed: cfg.VspClosed,
}
err = webapi.Start(ctx, shutdownRequestChannel, &shutdownWg, cfg.Listen, db,
dcrdConnect, walletConnect, cfg.WebServerDebug, cfg.FeeXPub, apiCfg)
dcrd, wallets, cfg.WebServerDebug, cfg.FeeXPub, apiCfg)
if err != nil {
log.Errorf("Failed to initialize webapi: %v", err)
requestShutdown()

View File

@ -22,14 +22,16 @@ type Caller interface {
Call(ctx context.Context, method string, res interface{}, args ...interface{}) error
}
// Connect dials and returns a connected RPC client.
type Connect func() (Caller, error)
// connect dials and returns a connected RPC client. A boolean indicates whether
// this connection is new (true), or if it is an existing connection which is
// being reused (false).
type connect func() (Caller, bool, error)
// Setup accepts RPC connection details, creates an RPC client, and returns a
// setup accepts RPC connection details, creates an RPC client, and returns a
// function which can be called to access the client. The returned function will
// try to handle any client disconnects by attempting to reconnect, but will
// return an error if a new connection cannot be established.
func Setup(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass, addr string, cert []byte, n wsrpc.Notifier) Connect {
func setup(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass, addr string, cert []byte, n wsrpc.Notifier) connect {
// Create TLS options.
pool := x509.NewCertPool()
@ -67,7 +69,7 @@ func Setup(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass, addr str
shutdownWg.Done()
}()
return func() (Caller, error) {
return func() (Caller, bool, error) {
defer mu.Unlock()
mu.Lock()
@ -77,16 +79,16 @@ func Setup(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass, addr str
log.Debugf("RPC client %s errored (%v); reconnecting...", addr, c.Err())
c = nil
default:
return c, nil
return c, false, nil
}
}
var err error
c, err = wsrpc.Dial(ctx, fullAddr, tlsOpt, authOpt, wsrpc.WithNotifier(n))
if err != nil {
return nil, err
return nil, false, err
}
return c, nil
return c, true, nil
}
}

View File

@ -5,12 +5,14 @@ import (
"encoding/hex"
"errors"
"fmt"
"sync"
"github.com/decred/dcrd/blockchain/stake/v3"
"github.com/decred/dcrd/chaincfg/v3"
dcrdtypes "github.com/decred/dcrd/rpc/jsonrpc/types/v2"
"github.com/decred/dcrd/wire"
"github.com/jrick/bitset"
"github.com/jrick/wsrpc/v2"
)
const (
@ -24,18 +26,36 @@ type DcrdRPC struct {
ctx context.Context
}
// DcrdClient creates a new DcrdRPC client instance from a caller.
func DcrdClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*DcrdRPC, error) {
type DcrdConnect connect
func SetupDcrd(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass, addr string, cert []byte, n wsrpc.Notifier) DcrdConnect {
return DcrdConnect(setup(ctx, shutdownWg, user, pass, addr, cert, n))
}
// Client creates a new DcrdRPC client instance. Returns an error if dialing
// dcrd fails or if dcrd is misconfigured.
func (d *DcrdConnect) Client(ctx context.Context, netParams *chaincfg.Params) (*DcrdRPC, error) {
c, newConnection, err := connect(*d)()
if err != nil {
return nil, fmt.Errorf("dcrd connection error: %v", err)
}
// If this is a reused connection, we don't need to validate the dcrd config
// again.
if !newConnection {
return &DcrdRPC{c, ctx}, nil
}
// Verify dcrd is at the required api version.
var verMap map[string]dcrdtypes.VersionResult
err := c.Call(ctx, "version", &verMap)
err = c.Call(ctx, "version", &verMap)
if err != nil {
return nil, fmt.Errorf("version check failed: %v", err)
return nil, fmt.Errorf("dcrd version check failed: %v", err)
}
dcrdVersion, exists := verMap["dcrdjsonrpcapi"]
if !exists {
return nil, fmt.Errorf("version response missing 'dcrdjsonrpcapi'")
return nil, fmt.Errorf("dcrd version response missing 'dcrdjsonrpcapi'")
}
if dcrdVersion.VersionString != requiredDcrdVersion {
return nil, fmt.Errorf("wrong dcrd RPC version: got %s, expected %s",
@ -46,7 +66,7 @@ func DcrdClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*Dcr
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("dcrd getcurrentnet check failed: %v", err)
}
if netID != netParams.Net {
return nil, fmt.Errorf("dcrd running on %s, expected %s", netID, netParams.Net)
@ -56,7 +76,7 @@ func DcrdClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*Dcr
var info dcrdtypes.InfoChainResult
err = c.Call(ctx, "getinfo", &info)
if err != nil {
return nil, fmt.Errorf("getinfo check failed: %v", err)
return nil, fmt.Errorf("dcrd getinfo check failed: %v", err)
}
if !info.TxIndex {
return nil, errors.New("dcrd does not have transaction index enabled (--txindex)")

View File

@ -3,6 +3,7 @@ package rpc
import (
"context"
"fmt"
"sync"
wallettypes "decred.org/dcrwallet/rpc/jsonrpc/types"
"github.com/decred/dcrd/chaincfg/v3"
@ -21,12 +22,41 @@ type WalletRPC struct {
ctx context.Context
}
// WalletClient creates a new WalletRPC client instance from a caller.
func WalletClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*WalletRPC, error) {
type WalletConnect []connect
func SetupWallet(ctx context.Context, shutdownWg *sync.WaitGroup, user, pass string, addrs []string, cert []byte) WalletConnect {
walletConnect := make(WalletConnect, len(addrs))
for i := 0; i < len(addrs); i++ {
walletConnect[i] = setup(ctx, shutdownWg, user, pass,
addrs[i], cert, nil)
}
return walletConnect
}
// Clients creates an array of new WalletRPC client instances. Returns an error
// if dialing any wallet fails, or if any wallet is misconfigured.
func (w *WalletConnect) Clients(ctx context.Context, netParams *chaincfg.Params) ([]*WalletRPC, error) {
walletClients := make([]*WalletRPC, len(*w))
for i := 0; i < len(*w); i++ {
c, newConnection, err := []connect(*w)[i]()
if err != nil {
return nil, fmt.Errorf("dcrwallet connection error: %v", err)
}
// If this is a reused connection, we don't need to validate the
// dcrwallet config again.
if !newConnection {
walletClients[i] = &WalletRPC{c, ctx}
continue
}
// Verify dcrwallet is at the required api version.
var verMap map[string]dcrdtypes.VersionResult
err := c.Call(ctx, "version", &verMap)
err = c.Call(ctx, "version", &verMap)
if err != nil {
return nil, fmt.Errorf("version check on dcrwallet '%s' failed: %v",
c.String(), err)
@ -48,6 +78,12 @@ func WalletClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*W
return nil, fmt.Errorf("walletinfo check on dcrwallet '%s' failed: %v",
c.String(), err)
}
// TODO: The following 3 checks should probably just log a warning/error and
// not return.
// addtransaction and setvotechoice can still be used with a locked wallet.
// importprivkey will fail if wallet is locked.
if !walletInfo.Voting {
return nil, fmt.Errorf("wallet '%s' has voting disabled", c.String())
}
@ -70,7 +106,11 @@ func WalletClient(ctx context.Context, c Caller, netParams *chaincfg.Params) (*W
c.String(), netID, netParams.Net)
}
return &WalletRPC{c, ctx}, nil
walletClients[i] = &WalletRPC{c, ctx}
}
return walletClients, nil
}
func (c *WalletRPC) AddTransaction(blockHash, txHex string) error {

View File

@ -16,43 +16,28 @@ type ticketHashRequest struct {
// context for downstream handlers to make use of.
func withDcrdClient() gin.HandlerFunc {
return func(c *gin.Context) {
dcrdConn, err := dcrdConnect()
client, err := dcrd.Client(c, cfg.NetParams)
if err != nil {
log.Errorf("dcrd connection error: %v", err)
sendErrorResponse("dcrd RPC error", http.StatusInternalServerError, c)
return
}
dcrdClient, err := rpc.DcrdClient(c, dcrdConn, cfg.NetParams)
if err != nil {
log.Errorf("dcrd client error: %v", err)
log.Error(err)
sendErrorResponse("dcrd RPC error", http.StatusInternalServerError, c)
return
}
c.Set("DcrdClient", dcrdClient)
c.Set("DcrdClient", client)
}
}
// withWalletClient middleware adds a voting wallet client to the request
// withWalletClients middleware adds a voting wallet clients to the request
// context for downstream handlers to make use of.
func withWalletClient() gin.HandlerFunc {
func withWalletClients() gin.HandlerFunc {
return func(c *gin.Context) {
walletClient := make([]*rpc.WalletRPC, len(walletConnect))
for i := 0; i < len(walletConnect); i++ {
walletConn, err := walletConnect[i]()
clients, err := wallets.Clients(c, cfg.NetParams)
if err != nil {
log.Errorf("dcrwallet connection error: %v", err)
log.Error(err)
sendErrorResponse("dcrwallet RPC error", http.StatusInternalServerError, c)
return
}
walletClient[i], 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)
c.Set("WalletClients", clients)
}
}

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)
walletClients := c.MustGet("WalletClient").([]*rpc.WalletRPC)
walletClients := c.MustGet("WalletClients").([]*rpc.WalletRPC)
if !knownTicket {
log.Warnf("Invalid ticket from %s", c.ClientIP())

View File

@ -34,19 +34,19 @@ const (
var cfg Config
var db *database.VspDatabase
var dcrdConnect rpc.Connect
var walletConnect []rpc.Connect
var dcrd rpc.DcrdConnect
var wallets rpc.WalletConnect
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.DcrdConnect, wConnect rpc.WalletConnect, debugMode bool, feeXPub string, config Config) error {
cfg = config
db = vdb
dcrdConnect = dConnect
walletConnect = wConnect
dcrd = dConnect
wallets = wConnect
var err error
@ -184,7 +184,7 @@ func router(debugMode bool) *gin.Engine {
// These API routes access dcrd and the voting wallets, and they need
// authentication.
both := router.Group("/api").Use(
withDcrdClient(), withWalletClient(), vspAuth(),
withDcrdClient(), withWalletClients(), vspAuth(),
)
both.POST("/setvotechoices", setVoteChoices)