From 6ed4e30b6d343cb3097dc134846521191a3277a9 Mon Sep 17 00:00:00 2001 From: Marco Argentieri <3596602+tiero@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:49:00 +0100 Subject: [PATCH] Support connecting to bitcoind via ZMQ (#286) * add ZMQ env vars * consolidate config and app-config naming --- server/Makefile | 2 + server/cmd/arkd/main.go | 2 + server/internal/app-config/config.go | 18 ++++-- server/internal/config/config.go | 6 ++ .../wallet/btc-embedded/wallet.go | 58 ++++++++++++++++++- 5 files changed, 79 insertions(+), 7 deletions(-) diff --git a/server/Makefile b/server/Makefile index 608d7ff..bfa5d97 100755 --- a/server/Makefile +++ b/server/Makefile @@ -44,6 +44,8 @@ run: clean export ARK_BITCOIND_RPC_USER=admin1; \ export ARK_BITCOIND_RPC_PASS=123; \ export ARK_BITCOIND_RPC_HOST=localhost:18443; \ + export ARK_BITCOIND_ZMQ_BLOCK=tcp://127.0.0.1:28332; \ + export ARK_BITCOIND_ZMQ_TX=tcp://127.0.0.1:28333; \ export ARK_DATADIR=./data/regtest; \ go run ./cmd/arkd diff --git a/server/cmd/arkd/main.go b/server/cmd/arkd/main.go index 8ca503c..b97b248 100755 --- a/server/cmd/arkd/main.go +++ b/server/cmd/arkd/main.go @@ -77,6 +77,8 @@ func mainAction(_ *cli.Context) error { BitcoindRpcUser: cfg.BitcoindRpcUser, BitcoindRpcPass: cfg.BitcoindRpcPass, BitcoindRpcHost: cfg.BitcoindRpcHost, + BitcoindZMQBlock: cfg.BitcoindZMQBlock, + BitcoindZMQTx: cfg.BitcoindZMQTx, BoardingExitDelay: cfg.BoardingExitDelay, UnlockerType: cfg.UnlockerType, UnlockerFilePath: cfg.UnlockerFilePath, diff --git a/server/internal/app-config/config.go b/server/internal/app-config/config.go index fadbb38..fa579fa 100644 --- a/server/internal/app-config/config.go +++ b/server/internal/app-config/config.go @@ -70,11 +70,13 @@ type Config struct { NostrDefaultRelays []string NoteUriPrefix string - EsploraURL string - NeutrinoPeer string - BitcoindRpcUser string - BitcoindRpcPass string - BitcoindRpcHost string + EsploraURL string + NeutrinoPeer string + BitcoindRpcUser string + BitcoindRpcPass string + BitcoindRpcHost string + BitcoindZMQBlock string + BitcoindZMQTx string UnlockerType string UnlockerFilePath string // file unlocker @@ -271,12 +273,16 @@ func (c *Config) walletService() error { var err error switch { + case c.BitcoindZMQBlock != "" && c.BitcoindZMQTx != "" && c.BitcoindRpcUser != "" && c.BitcoindRpcPass != "": + svc, err = btcwallet.NewService(btcwallet.WalletConfig{ + Datadir: c.DbDir, + Network: c.Network, + }, btcwallet.WithBitcoindZMQ(c.BitcoindZMQBlock, c.BitcoindZMQTx, c.BitcoindRpcHost, c.BitcoindRpcUser, c.BitcoindRpcPass)) case c.BitcoindRpcUser != "" && c.BitcoindRpcPass != "": svc, err = btcwallet.NewService(btcwallet.WalletConfig{ Datadir: c.DbDir, Network: c.Network, }, btcwallet.WithPollingBitcoind(c.BitcoindRpcHost, c.BitcoindRpcUser, c.BitcoindRpcPass)) - default: // Default to Neutrino for Bitcoin mainnet or when NeutrinoPeer is explicitly set if len(c.EsploraURL) == 0 { diff --git a/server/internal/config/config.go b/server/internal/config/config.go index 718c960..9a1747b 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -33,6 +33,8 @@ type Config struct { BitcoindRpcUser string BitcoindRpcPass string BitcoindRpcHost string + BitcoindZMQBlock string + BitcoindZMQTx string TLSExtraIPs []string TLSExtraDomains []string UnlockerType string @@ -65,6 +67,8 @@ var ( // #nosec G101 BitcoindRpcPass = "BITCOIND_RPC_PASS" BitcoindRpcHost = "BITCOIND_RPC_HOST" + BitcoindZMQBlock = "BITCOIND_ZMQ_BLOCK" + BitcoindZMQTx = "BITCOIND_ZMQ_TX" NoMacaroons = "NO_MACAROONS" NoTLS = "NO_TLS" TLSExtraIP = "TLS_EXTRA_IP" @@ -145,6 +149,8 @@ func LoadConfig() (*Config, error) { BitcoindRpcUser: viper.GetString(BitcoindRpcUser), BitcoindRpcPass: viper.GetString(BitcoindRpcPass), BitcoindRpcHost: viper.GetString(BitcoindRpcHost), + BitcoindZMQBlock: viper.GetString(BitcoindZMQBlock), + BitcoindZMQTx: viper.GetString(BitcoindZMQTx), NoMacaroons: viper.GetBool(NoMacaroons), TLSExtraIPs: viper.GetStringSlice(TLSExtraIP), TLSExtraDomains: viper.GetStringSlice(TLSExtraDomain), diff --git a/server/internal/infrastructure/wallet/btc-embedded/wallet.go b/server/internal/infrastructure/wallet/btc-embedded/wallet.go index f0c5545..cd651c0 100644 --- a/server/internal/infrastructure/wallet/btc-embedded/wallet.go +++ b/server/internal/infrastructure/wallet/btc-embedded/wallet.go @@ -193,7 +193,7 @@ func WithPollingBitcoind(host, user, pass string) WalletOption { }, } - chain.UseLogger(logger("chain")) + btcwallet.UseLogger(logger("btcwallet")) // Create the BitcoindConn first bitcoindConn, err := chain.NewBitcoindConn(bitcoindConfig) @@ -258,6 +258,62 @@ func WithPollingBitcoind(host, user, pass string) WalletOption { } } +func WithBitcoindZMQ(block, tx string, host, user, pass string) WalletOption { + return func(s *service) error { + if s.chainSource != nil { + return fmt.Errorf("chain source already set") + } + + bitcoindConfig := &chain.BitcoindConfig{ + ChainParams: s.cfg.chainParams(), + Host: host, + User: user, + Pass: pass, + ZMQConfig: &chain.ZMQConfig{ + ZMQBlockHost: block, + ZMQTxHost: tx, + ZMQReadDeadline: 5 * time.Second, + }, + } + + btcwallet.UseLogger(logger("btcwallet")) + + bitcoindConn, err := chain.NewBitcoindConn(bitcoindConfig) + if err != nil { + return fmt.Errorf("failed to create bitcoind connection: %w", err) + } + + if err := bitcoindConn.Start(); err != nil { + return fmt.Errorf("failed to start bitcoind connection: %w", err) + } + + chainClient := bitcoindConn.NewBitcoindClient() + + if err := chainClient.Start(); err != nil { + bitcoindConn.Stop() + return fmt.Errorf("failed to start bitcoind client: %w", err) + } + + for !chainClient.IsCurrent() { + time.Sleep(1 * time.Second) + } + + if err := withChainSource(chainClient)(s); err != nil { + chainClient.Stop() + bitcoindConn.Stop() + return fmt.Errorf("failed to set chain source: %w", err) + } + + if err := withScanner(chainClient)(s); err != nil { + chainClient.Stop() + bitcoindConn.Stop() + return fmt.Errorf("failed to set scanner: %w", err) + } + + return nil + } +} + // NewService creates the wallet service, an option must be set to configure the chain source. func NewService(cfg WalletConfig, options ...WalletOption) (ports.WalletService, error) { wallet.UseLogger(logger("wallet"))