Skip to content

Commit

Permalink
chain: add NewRPCClientWithConfig to init client using config
Browse files Browse the repository at this point in the history
This commit adds a new method to init the RPC client. Unlike the
`NewRPCClient`, it now takes a config to allow users specifying
notification handlers.

In the future, `NewRPCClient` should be replaced by this method. For now
we implement it this way so other callsites relying on this method can
stay out of being affected.
  • Loading branch information
yyforyongyu committed Mar 4, 2024
1 parent edb6ec9 commit 524b1eb
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 0 deletions.
105 changes: 105 additions & 0 deletions chain/btcd.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var _ Interface = (*RPCClient)(nil)
// but must be done using the Start method. If the remote server does not
// operate on the same bitcoin network as described by the passed chain
// parameters, the connection will be disconnected.
//
// TODO(yy): deprecate it in favor of NewRPCClientWithConfig.
func NewRPCClient(chainParams *chaincfg.Params, connect, user, pass string, certs []byte,
disableTLS bool, reconnectAttempts int) (*RPCClient, error) {

Expand Down Expand Up @@ -91,6 +93,109 @@ func NewRPCClient(chainParams *chaincfg.Params, connect, user, pass string, cert
return client, nil
}

// RPCClientConfig defines the config options used when initializing the RPC
// Client.
type RPCClientConfig struct {
// Conn describes the connection configuration parameters for the
// client.
Conn *rpcclient.ConnConfig

// Params defines a Bitcoin network by its parameters.
Chain *chaincfg.Params

// NotificationHandlers defines callback function pointers to invoke
// with notifications. If not set, the default handlers defined in this
// client will be used.
NotificationHandlers *rpcclient.NotificationHandlers

// ReconnectAttempts defines the number to reties (each after an
// increasing backoff) if the connection can not be established.
ReconnectAttempts int
}

// validate checks the required config options are set.
func (r *RPCClientConfig) validate() error {
if r == nil {
return errors.New("missing rpc config")
}

// Make sure retry attempts is positive.
if r.ReconnectAttempts < 0 {
return errors.New("reconnectAttempts must be positive")
}

// Make sure the chain params are configed.
if r.Chain == nil {
return errors.New("missing chain params config")
}

// Make sure connection config is supplied.
if r.Conn == nil {
return errors.New("missing conn config")
}

// If disableTLS is false, the remote RPC certificate must be provided
// in the certs slice.
if !r.Conn.DisableTLS && r.Conn.Certificates == nil {
return errors.New("must provide certs when TLS is enabled")
}

return nil
}

// NewRPCClientWithConfig creates a client connection to the server based on
// the config options supplised.
//
// The connection is not established immediately, but must be done using the
// Start method. If the remote server does not operate on the same bitcoin
// network as described by the passed chain parameters, the connection will be
// disconnected.
func NewRPCClientWithConfig(cfg *RPCClientConfig) (*RPCClient, error) {
// Make sure the config is valid.
if err := cfg.validate(); err != nil {
return nil, err
}

// Mimic the old behavior defined in `NewRPCClient`. We will remove
// these hard-codings once this package is more properly refactored.
cfg.Conn.DisableAutoReconnect = false
cfg.Conn.DisableConnectOnNew = true

client := &RPCClient{
connConfig: cfg.Conn,
chainParams: cfg.Chain,
reconnectAttempts: cfg.ReconnectAttempts,
enqueueNotification: make(chan interface{}),
dequeueNotification: make(chan interface{}),
currentBlock: make(chan *waddrmgr.BlockStamp),
quit: make(chan struct{}),
}

// Use the configed notification callbacks, if not set, default to the
// callbacks defined in this package.
ntfnCallbacks := cfg.NotificationHandlers
if ntfnCallbacks == nil {
ntfnCallbacks = &rpcclient.NotificationHandlers{
OnClientConnected: client.onClientConnect,
OnBlockConnected: client.onBlockConnected,
OnBlockDisconnected: client.onBlockDisconnected,
OnRecvTx: client.onRecvTx,
OnRedeemingTx: client.onRedeemingTx,
OnRescanFinished: client.onRescanFinished,
OnRescanProgress: client.onRescanProgress,
}
}

// Create the RPC client using the above config.
rpcClient, err := rpcclient.New(client.connConfig, ntfnCallbacks)
if err != nil {
return nil, err
}

client.Client = rpcClient
return client, nil
}

// BackEnd returns the name of the driver.
func (c *RPCClient) BackEnd() string {
return "btcd"
Expand Down
58 changes: 58 additions & 0 deletions chain/btcd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package chain

import (
"testing"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/rpcclient"
"github.com/stretchr/testify/require"
)

// TestValidateConfig checks the `validate` method on the RPCClientConfig
// behaves as expected.
func TestValidateConfig(t *testing.T) {
t.Parallel()

rt := require.New(t)

// ReconnectAttempts must be positive.
cfg := &RPCClientConfig{
ReconnectAttempts: -1,
}
rt.ErrorContains(cfg.validate(), "reconnectAttempts")

// Must specify a chain params.
cfg = &RPCClientConfig{
ReconnectAttempts: 1,
}
rt.ErrorContains(cfg.validate(), "chain params")

// Must specify a connection config.
cfg = &RPCClientConfig{
ReconnectAttempts: 1,
Chain: &chaincfg.Params{},
}
rt.ErrorContains(cfg.validate(), "conn config")

// Must specify a certificate when using TLS.
cfg = &RPCClientConfig{
ReconnectAttempts: 1,
Chain: &chaincfg.Params{},
Conn: &rpcclient.ConnConfig{},
}
rt.ErrorContains(cfg.validate(), "certs")

// Validate config.
cfg = &RPCClientConfig{
ReconnectAttempts: 1,
Chain: &chaincfg.Params{},
Conn: &rpcclient.ConnConfig{
DisableTLS: true,
},
}
rt.NoError(cfg.validate())

// When a nil config is provided, it should return an error.
_, err := NewRPCClientWithConfig(nil)
rt.ErrorContains(err, "missing rpc config")
}

0 comments on commit 524b1eb

Please sign in to comment.