Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for testnet4 and Legacy RPC Compatibility with bitcoind Backend #985

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 67 additions & 4 deletions btcwallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package main

import (
"fmt"
"net"
"net/http"
_ "net/http/pprof" // nolint:gosec
Expand All @@ -14,10 +15,13 @@ import (
"sync"

"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/internal/cfgutil"
"github.com/btcsuite/btcwallet/rpc/legacyrpc"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/lightninglabs/neutrino"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

var (
Expand Down Expand Up @@ -188,7 +192,7 @@ func rpcClientConnectLoop(legacyRPCServer *legacyrpc.Server, loader *wallet.Load
log.Errorf("Couldn't start Neutrino client: %s", err)
}
} else {
chainClient, err = startChainRPC(certs)
chainClient, err = startChain(certs)
if err != nil {
log.Errorf("Unable to open connection to consensus RPC server: %v", err)
continue
Expand Down Expand Up @@ -265,13 +269,72 @@ func readCAFile() []byte {
// services. This function uses the RPC options from the global config and
// there is no recovery in case the server is not available or if there is an
// authentication error. Instead, all requests to the client will simply error.
func startChainRPC(certs []byte) (*chain.RPCClient, error) {
func startChainRPC(certs []byte) (chain.Interface, error) {
log.Infof("Attempting RPC client connection to %v", cfg.RPCConnect)

rpcc, err := chain.NewRPCClient(activeNet.Params, cfg.RPCConnect,
cfg.BtcdUsername, cfg.BtcdPassword, certs, cfg.DisableClientTLS, 0)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create chain bitcoind conn: %v", err)
}

err = rpcc.Start()
return rpcc, err
if err != nil {
return nil, fmt.Errorf("unable to start chain bitcoind client: %v", err)
}
log.Infof("RPC client connected to %v", cfg.RPCConnect)
return rpcc, nil
}

// startChainBitcoind opens a connection to a bitcoind/bitcoind-like RPC server
// for blockchain services. This function uses the RPC options from the global
// config and there is no recovery in case the server is not available or if
// there is an authentication error. Instead, all requests to the client will
// simply error.
func startChainBitcoind() (chain.Interface, error) {
log.Infof("Attempting to connect to a bitcoind node. Address: %v", cfg.RPCConnect)
networkAddress, err := cfgutil.NormalizeAddress(cfg.RPCConnect,
activeNet.RPCClientPort)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument,
"Network address is ill-formed: %v", err)
}

c, err := chain.NewBitcoindConn(&chain.BitcoindConfig{
ChainParams: activeNet.Params,
Host: networkAddress,
User: cfg.Username,
Pass: cfg.Password,
// ZMQConfig: &chain.ZMQConfig{}, // TODO: add ZMQ support
PollingConfig: &chain.PollingConfig{}, // TODO: add customize polling support
})
if err != nil {
return nil, fmt.Errorf("unable to create bitcoind conn: %v", err)
}

if err := c.Start(); err != nil {
return nil, fmt.Errorf("unable to start bitcoind conn: %v", err)
}

bdc := c.NewBitcoindClient()

err = bdc.Start()
if err != nil {
return nil, fmt.Errorf("unable to start chain bitcoind client: %v", err)
}
log.Infof("Connected successfully to a bitcoind node.")

return bdc, nil
}

func startChain(certs []byte) (chain.Interface, error) {
switch cfg.Backend {
case chain.Bitcoind:
return startChainBitcoind()
case chain.Btcd:
return startChainRPC(certs)
default:
return nil, fmt.Errorf("unknown chain backend: %v", cfg.Backend)
}

}
15 changes: 9 additions & 6 deletions chain/bitcoind_conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,18 +148,18 @@ func NewBitcoindConn(cfg *BitcoindConfig) (*BitcoindConn, error) {
}
client, err := rpcclient.New(clientCfg, nil)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to connect to bitcoind: %w", err)
}

batchClient, err := rpcclient.NewBatch(clientCfg)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to connect to bitcoind batch: %w", err)
}

// Verify that the node is running on the expected network.
net, err := getCurrentNet(client)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to determine bitcoind network: %w", err)
}
if net != cfg.ChainParams.Net {
return nil, fmt.Errorf("expected network %v, got %v",
Expand Down Expand Up @@ -191,7 +191,8 @@ func NewBitcoindConn(cfg *BitcoindConfig) (*BitcoindConn, error) {
},
)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create pruned block "+
"dispatcher: %w", err)
}
}

Expand All @@ -205,7 +206,8 @@ func NewBitcoindConn(cfg *BitcoindConfig) (*BitcoindConn, error) {

bc.events, err = NewBitcoindEventSubscriber(cfg, client, batchClient)
if err != nil {
return nil, err
return nil, fmt.Errorf("unable to create bitcoind event "+
"subscriber: %w", err)
}

return bc, nil
Expand Down Expand Up @@ -381,10 +383,11 @@ func getCurrentNet(client *rpcclient.Client) (wire.BitcoinNet, error) {
if err != nil {
return 0, err
}

switch *hash {
case *chaincfg.TestNet3Params.GenesisHash:
return chaincfg.TestNet3Params.Net, nil
case *chaincfg.TestNet4Params.GenesisHash:
return chaincfg.TestNet4Params.Net, nil
case *chaincfg.RegressionNetParams.GenesisHash:
return chaincfg.RegressionNetParams.Net, nil
case *chaincfg.SigNetParams.GenesisHash:
Expand Down
22 changes: 18 additions & 4 deletions chain/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,31 @@ import (
// the chain.
const isCurrentDelta = 2 * time.Hour

type BackendServer string

const (
// Bitcoind is the backend type for a bitcoind/bitcoind-like RPC server.
Bitcoind BackendServer = "bitcoind"
Btcd BackendServer = "btcd"
Neutrino BackendServer = "neutrino"
)

// BackEnds returns a list of the available back ends.
// TODO: Refactor each into a driver and use dynamic registration.
func BackEnds() []string {
return []string{
"bitcoind",
"btcd",
"neutrino",
"bitcoind-rpc-polling",
string(Bitcoind),
string(Btcd),
string(Neutrino),
"bitcoind-rpc-polling", // not clear what this is
}
}

func SupportedBackEnds() []BackendServer {
return []BackendServer{Bitcoind, Btcd}

}

// Interface allows more than one backing blockchain source, such as a
// btcd RPC chain server, or an SPV library, as long as we write a driver for
// it.
Expand Down
15 changes: 15 additions & 0 deletions cmd/sweepaccount/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import (
"fmt"
"os"
"path/filepath"
"slices"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/rpcclient"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/internal/cfgutil"
"github.com/btcsuite/btcwallet/netparams"
"github.com/btcsuite/btcwallet/wallet/txauthor"
Expand Down Expand Up @@ -42,8 +44,10 @@ func errContext(err error, context string) error {
// Flags.
var opts = struct {
TestNet3 bool `long:"testnet" description:"Use the test bitcoin network (version 3)"`
TestNet4 bool `long:"testnet4" description:"Use the test bitcoin network (version 4)"`
SimNet bool `long:"simnet" description:"Use the simulation bitcoin network"`
RegressionNet bool `long:"regtest" description:"Use the regression bitcoin network"`
Backend string `short:"b" long:"backend" description:"The wallet backend to use" default:"btcd" choice:"btcd" choice:"bitcoind"`
RPCConnect string `short:"c" long:"connect" description:"Hostname[:port] of wallet RPC server"`
RPCUsername string `short:"u" long:"rpcuser" description:"Wallet RPC username"`
RPCCertificateFile string `long:"cafile" description:"Wallet RPC TLS certificate"`
Expand All @@ -53,8 +57,10 @@ var opts = struct {
RequiredConfirmations int64 `long:"minconf" description:"Required confirmations to include an output"`
}{
TestNet3: false,
TestNet4: false,
SimNet: false,
RegressionNet: false,
Backend: "btcd",
RPCConnect: "localhost",
RPCUsername: "",
RPCCertificateFile: filepath.Join(walletDataDirectory, "rpc.cert"),
Expand Down Expand Up @@ -85,6 +91,9 @@ func init() {
if opts.TestNet3 {
numNets++
}
if opts.TestNet4 {
numNets++
}
if opts.SimNet {
numNets++
}
Expand All @@ -97,6 +106,8 @@ func init() {
var activeNet = &netparams.MainNetParams
if opts.TestNet3 {
activeNet = &netparams.TestNet3Params
} else if opts.TestNet4 {
activeNet = &netparams.TestNet4Params
} else if opts.SimNet {
activeNet = &netparams.SimNetParams
} else if opts.RegressionNet {
Expand All @@ -116,6 +127,10 @@ func init() {
fatalf("RPC username is required")
}

if !slices.Contains(chain.SupportedBackEnds(), chain.BackendServer(opts.Backend)) {
fatalf("Invalid backend `%s`, supported backends are %v", opts.Backend, chain.SupportedBackEnds())
}

certFileExists, err = cfgutil.FileExists(opts.RPCCertificateFile)
if err != nil {
fatalf("%v", err)
Expand Down
24 changes: 23 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import (
"os/user"
"path/filepath"
"runtime"
"slices"
"sort"
"strings"
"time"

"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/internal/cfgutil"
"github.com/btcsuite/btcwallet/internal/legacy/keystore"
"github.com/btcsuite/btcwallet/netparams"
Expand All @@ -34,6 +36,7 @@ const (
defaultLogFilename = "btcwallet.log"
defaultRPCMaxClients = 10
defaultRPCMaxWebsockets = 25
defaultBE = "btcd"
)

var (
Expand All @@ -54,6 +57,7 @@ type config struct {
CreateTemp bool `long:"createtemp" description:"Create a temporary simulation wallet (pass=password) in the data directory indicated; must call with --datadir"`
AppDataDir *cfgutil.ExplicitString `short:"A" long:"appdata" description:"Application data directory for wallet config, databases and logs"`
TestNet3 bool `long:"testnet" description:"Use the test Bitcoin network (version 3) (default mainnet)"`
TestNet4 bool `long:"testnet4" description:"Use the test Bitcoin network (version 4) (default mainnet)"`
SimNet bool `long:"simnet" description:"Use the simulation test network (default mainnet)"`
SigNet bool `long:"signet" description:"Use the signet test network (default mainnet)"`
SigNetChallenge string `long:"signetchallenge" description:"Connect to a custom signet network defined by this challenge instead of using the global default signet test network -- Can be specified multiple times"`
Expand All @@ -68,6 +72,12 @@ type config struct {
// Wallet options
WalletPass string `long:"walletpass" default-mask:"-" description:"The public wallet password -- Only required if the wallet was created with one"`

// Backend options
// this will determine the backend used for the wallet, based on this the wallet will use the appropriate RPC client
// to communicate with the backend
// TODO: Add support for backend-specific configurations. this should either be a configuration file or a set of flags. preferably a configuration file
Backend chain.BackendServer `long:"backend" description:"The backend to use for the wallet" choice:"btcd" choice:"bitcoind" default:"btcd"`

// RPC client options
RPCConnect string `short:"c" long:"rpcconnect" description:"Hostname/IP and port of btcd RPC server to connect to (default localhost:8334, testnet: localhost:18334, simnet: localhost:18556, regtest: localhost:18334)"`
CAFile *cfgutil.ExplicitString `long:"cafile" description:"File containing root certificates to authenticate a TLS connections with btcd"`
Expand Down Expand Up @@ -98,7 +108,7 @@ type config struct {
RPCKey *cfgutil.ExplicitString `long:"rpckey" description:"File containing the certificate key"`
OneTimeTLSKey bool `long:"onetimetlskey" description:"Generate a new TLS certpair at startup, but only write the certificate to disk"`
DisableServerTLS bool `long:"noservertls" description:"Disable TLS for the RPC server -- NOTE: This is only allowed if the RPC server is bound to localhost"`
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 8332, testnet: 18332, simnet: 18554, regtest: 18332)"`
LegacyRPCListeners []string `long:"rpclisten" description:"Listen for legacy RPC connections on this interface/port (default port: 8332, testnet: 18332, testnet4: 4833, simnet: 18554, regtest: 18332)"`
LegacyRPCMaxClients int64 `long:"rpcmaxclients" description:"Max number of legacy RPC clients for standard connections"`
LegacyRPCMaxWebsockets int64 `long:"rpcmaxwebsockets" description:"Max number of legacy RPC websocket connections"`
Username string `short:"u" long:"username" description:"Username for legacy RPC and btcd authentication (if btcdusername is unset)"`
Expand Down Expand Up @@ -280,6 +290,7 @@ func loadConfig() (*config, []string, error) {
BanDuration: neutrino.BanDuration,
BanThreshold: neutrino.BanThreshold,
DBTimeout: wallet.DefaultDBTimeout,
Backend: defaultBE,
}

// Pre-parse the command line options to see if an alternative config
Expand Down Expand Up @@ -338,6 +349,13 @@ func loadConfig() (*config, []string, error) {
return nil, nil, err
}

if !slices.Contains(chain.SupportedBackEnds(), cfg.Backend) {
err := fmt.Errorf("invalid backend %s", cfg.Backend)
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}

// Check deprecated aliases. The new options receive priority when both
// are changed from the default.
if cfg.DataDir.ExplicitlySet() {
Expand Down Expand Up @@ -368,6 +386,10 @@ func loadConfig() (*config, []string, error) {
activeNet = &netparams.TestNet3Params
numNets++
}
if cfg.TestNet4 {
activeNet = &netparams.TestNet4Params
numNets++
}
if cfg.SimNet {
activeNet = &netparams.SimNetParams
numNets++
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
module github.com/btcsuite/btcwallet

replace github.com/btcsuite/btcd => github.com/bullet-tooth/btcd v0.0.0-20250227100521-6f8d6a01e16e

require (
github.com/btcsuite/btcd v0.24.2-beta.rc1.0.20240625142744-cc26860b4026
github.com/btcsuite/btcd/btcec/v2 v2.2.2
github.com/btcsuite/btcd/btcec/v2 v2.3.4
github.com/btcsuite/btcd/btcutil v1.1.5
github.com/btcsuite/btcd/btcutil/psbt v1.1.8
github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
Expand Down
Loading