From 3f0bb1985af4892281777ce85be90e5a8dae8fbd Mon Sep 17 00:00:00 2001 From: lazar Date: Thu, 22 Aug 2024 13:22:58 +0200 Subject: [PATCH 01/24] reporter uses notifier --- cmd/vigilante/cmd/reporter.go | 13 +++++++ e2etest/reporter_e2e_test.go | 28 +++++++++++++-- reporter/block_handler.go | 67 ++++++++++++++++++++++++----------- reporter/reporter.go | 4 +++ reporter/utils_test.go | 3 ++ 5 files changed, 91 insertions(+), 24 deletions(-) diff --git a/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index bbaa9317..aec059cc 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/babylonlabs-io/vigilante/netparams" bbnclient "github.com/babylonlabs-io/babylon/client/client" "github.com/spf13/cobra" @@ -62,12 +63,24 @@ func GetReporterCmd() *cobra.Command { // register reporter metrics reporterMetrics := metrics.NewReporterMetrics() + // create the chain notifier + btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams) + if err != nil { + panic(fmt.Errorf("failed to get BTC net params: %w", err)) + } + btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) // todo Lazar check if we should use concrete cache here? + if err != nil { + panic(fmt.Errorf("failed to initialize notifier: %w", err)) + } + // create reporter vigilantReporter, err = reporter.New( &cfg.Reporter, rootLogger, btcClient, babylonClient, + btcNotifier, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, reporterMetrics, diff --git a/e2etest/reporter_e2e_test.go b/e2etest/reporter_e2e_test.go index b269832c..f86b90f6 100644 --- a/e2etest/reporter_e2e_test.go +++ b/e2etest/reporter_e2e_test.go @@ -1,9 +1,8 @@ -//go:build e2e -// +build e2e - package e2etest import ( + "github.com/babylonlabs-io/vigilante/btcclient" + "github.com/babylonlabs-io/vigilante/netparams" "sync" "testing" "time" @@ -60,11 +59,19 @@ func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) { reporterMetrics := metrics.NewReporterMetrics() + // create the chain notifier + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) + require.NoError(t, err) + vigilantReporter, err := reporter.New( &tm.Config.Reporter, logger, tm.BTCClient, tm.BabylonClient, + btcNotifier, tm.Config.Common.RetrySleepTime, tm.Config.Common.MaxRetrySleepTime, reporterMetrics, @@ -116,11 +123,18 @@ func TestRelayHeadersAndHandleRollbacks(t *testing.T) { reporterMetrics := metrics.NewReporterMetrics() + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) + require.NoError(t, err) + vigilantReporter, err := reporter.New( &tm.Config.Reporter, logger, tm.BTCClient, tm.BabylonClient, + btcNotifier, tm.Config.Common.RetrySleepTime, tm.Config.Common.MaxRetrySleepTime, reporterMetrics, @@ -160,11 +174,18 @@ func TestHandleReorgAfterRestart(t *testing.T) { reporterMetrics := metrics.NewReporterMetrics() + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) + require.NoError(t, err) + vigilantReporter, err := reporter.New( &tm.Config.Reporter, logger, tm.BTCClient, tm.BabylonClient, + btcNotifier, tm.Config.Common.RetrySleepTime, tm.Config.Common.MaxRetrySleepTime, reporterMetrics, @@ -199,6 +220,7 @@ func TestHandleReorgAfterRestart(t *testing.T) { logger, btcClient, tm.BabylonClient, + btcNotifier, tm.Config.Common.RetrySleepTime, tm.Config.Common.MaxRetrySleepTime, reporterMetrics, diff --git a/reporter/block_handler.go b/reporter/block_handler.go index 1927d78a..dd9eff10 100644 --- a/reporter/block_handler.go +++ b/reporter/block_handler.go @@ -2,8 +2,8 @@ package reporter import ( "fmt" - "github.com/babylonlabs-io/vigilante/types" + "github.com/btcsuite/btcd/wire" ) // blockEventHandler handles connected and disconnected blocks from the BTC client. @@ -11,26 +11,50 @@ func (r *Reporter) blockEventHandler() { defer r.wg.Done() quit := r.quitChan() + if err := r.btcNotifier.Start(); err != nil { + r.logger.Errorf("Failed starting notifier") + return + } + + blockNotifier, err := r.btcNotifier.RegisterBlockEpochNtfn(nil) + if err != nil { + r.logger.Errorf("Failed registering block epoch notifier") + return + } + + defer blockNotifier.Cancel() + for { select { - case event, open := <-r.btcClient.BlockEventChan(): + case epoch, open := <-blockNotifier.Epochs: if !open { r.logger.Errorf("Block event channel is closed") return // channel closed } + tip := r.btcCache.Tip() + + // Determine if a reorg happened to we know which flow to continue + // if the new block has the same height but a different hash. + reorg := false + if tip != nil { + if epoch.Height < tip.Height || + (epoch.Height == tip.Height && epoch.BlockHeader.BlockHash() != tip.Header.BlockHash()) { + reorg = true + } + } + var errorRequiringBootstrap error - if event.EventType == types.BlockConnected { - errorRequiringBootstrap = r.handleConnectedBlocks(event) - } else if event.EventType == types.BlockDisconnected { - errorRequiringBootstrap = r.handleDisconnectedBlocks(event) + if !reorg { + errorRequiringBootstrap = r.handleConnectedBlocks(epoch.Height, epoch.BlockHeader) + } else { + errorRequiringBootstrap = r.handleDisconnectedBlocks(epoch.BlockHeader) } if errorRequiringBootstrap != nil { r.logger.Warnf("Due to error in event processing: %v, bootstrap process need to be restarted", errorRequiringBootstrap) r.bootstrapWithRetries(true) } - case <-quit: // We have been asked to stop return @@ -39,7 +63,7 @@ func (r *Reporter) blockEventHandler() { } // handleConnectedBlocks handles connected blocks from the BTC client. -func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { +func (r *Reporter) handleConnectedBlocks(height int32, header *wire.BlockHeader) error { // if the header is too early, ignore it // NOTE: this might happen when bootstrapping is triggered after the reporter // has subscribed to the BTC blocks @@ -47,11 +71,11 @@ func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { if firstCacheBlock == nil { return fmt.Errorf("cache is empty, restart bootstrap process") } - if event.Height < firstCacheBlock.Height { + if height < firstCacheBlock.Height { r.logger.Debugf( "the connecting block (height: %d, hash: %s) is too early, skipping the block", - event.Height, - event.Header.BlockHash().String(), + height, + header.BlockHash().String(), ) return nil } @@ -61,8 +85,8 @@ func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { // then ignore the block, otherwise there is an inconsistency and redo bootstrap // NOTE: this might happen when bootstrapping is triggered after the reporter // has subscribed to the BTC blocks - if b := r.btcCache.FindBlock(uint64(event.Height)); b != nil { - if b.BlockHash() == event.Header.BlockHash() { + if b := r.btcCache.FindBlock(uint64(height)); b != nil { + if b.BlockHash() == header.BlockHash() { r.logger.Debugf( "the connecting block (height: %d, hash: %s) is known to cache, skipping the block", b.Height, @@ -72,18 +96,18 @@ func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { } return fmt.Errorf( "the connecting block (height: %d, hash: %s) is different from the header (height: %d, hash: %s) at the same height in cache", - event.Height, - event.Header.BlockHash().String(), + height, + header.BlockHash().String(), b.Height, b.BlockHash().String(), ) } // get the block from hash - blockHash := event.Header.BlockHash() + blockHash := header.BlockHash() ib, mBlock, err := r.btcClient.GetBlockByHash(&blockHash) if err != nil { - return fmt.Errorf("failed to get block %v with number %d ,from BTC client: %w", blockHash, event.Height, err) + return fmt.Errorf("failed to get block %v with number %d ,from BTC client: %w", blockHash, height, err) } // if the parent of the block is not the tip of the cache, then the cache is not up-to-date, @@ -102,7 +126,7 @@ func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { if r.reorgList.size() > 0 { // we are in the middle of reorg, we need to check whether we already have all blocks of better chain // as reorgs in btc nodes happen only when better chain is available. - // 1. First we get oldest header from our reorg branch + // 1. First we get the oldest header from our reorg branch // 2. Then we get all headers from our cache starting the height of the oldest header of new branch // 3. then we calculate if work on new branch starting from the first reorged height is larger // than removed branch work. @@ -141,11 +165,12 @@ func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) error { if err != nil { r.logger.Warnf("Failed to submit checkpoint: %v", err) } + return nil } // handleDisconnectedBlocks handles disconnected blocks from the BTC client. -func (r *Reporter) handleDisconnectedBlocks(event *types.BlockEvent) error { +func (r *Reporter) handleDisconnectedBlocks(header *wire.BlockHeader) error { // get cache tip cacheTip := r.btcCache.Tip() if cacheTip == nil { @@ -153,11 +178,11 @@ func (r *Reporter) handleDisconnectedBlocks(event *types.BlockEvent) error { } // if the block to be disconnected is not the tip of the cache, then the cache is not up-to-date, - if event.Header.BlockHash() != cacheTip.BlockHash() { + if header.BlockHash() != cacheTip.BlockHash() { return fmt.Errorf("cache is not up-to-date while disconnecting block, restart bootstrap process") } - // at this point, the block to be disconnected is the tip of the cache so we can + // at this point, the block to be disconnected is the tip of the cache, so we can // add it to our reorg list r.reorgList.addRemovedBlock( uint64(cacheTip.Height), diff --git a/reporter/reporter.go b/reporter/reporter.go index 5a5bfb64..90d85529 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -3,6 +3,7 @@ package reporter import ( "encoding/hex" "fmt" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "sync" "time" @@ -22,6 +23,7 @@ type Reporter struct { btcClient btcclient.BTCClient babylonClient BabylonClient + btcNotifier notifier.ChainNotifier // retry attributes retrySleepTime time.Duration @@ -45,6 +47,7 @@ func New( parentLogger *zap.Logger, btcClient btcclient.BTCClient, babylonClient BabylonClient, + btcNotifier notifier.ChainNotifier, retrySleepTime, maxRetrySleepTime time.Duration, metrics *metrics.ReporterMetrics, @@ -81,6 +84,7 @@ func New( maxRetrySleepTime: maxRetrySleepTime, btcClient: btcClient, babylonClient: babylonClient, + btcNotifier: btcNotifier, CheckpointCache: ckptCache, reorgList: newReorgList(), btcConfirmationDepth: k, diff --git a/reporter/utils_test.go b/reporter/utils_test.go index d575c5ff..ab32234e 100644 --- a/reporter/utils_test.go +++ b/reporter/utils_test.go @@ -1,6 +1,7 @@ package reporter_test import ( + "github.com/lightningnetwork/lnd/lntest/mock" "math/rand" "testing" @@ -32,12 +33,14 @@ func newMockReporter(t *testing.T, ctrl *gomock.Controller) ( mockBabylonClient.EXPECT().GetConfig().Return(&cfg.Babylon).AnyTimes() mockBabylonClient.EXPECT().BTCCheckpointParams().Return( &btcctypes.QueryParamsResponse{Params: btccParams}, nil).AnyTimes() + mockNotifier := mock.ChainNotifier{} r, err := reporter.New( &cfg.Reporter, logger, mockBTCClient, mockBabylonClient, + &mockNotifier, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, metrics.NewReporterMetrics(), From 1991b216a98da8e3d7c9b087b817ba2494cda91b Mon Sep 17 00:00:00 2001 From: lazar Date: Thu, 22 Aug 2024 15:35:50 +0200 Subject: [PATCH 02/24] monitor uses notifier --- cmd/vigilante/cmd/monitor.go | 23 ++++++++++++- monitor/btcscanner/block_handler.go | 53 +++++++++++++++++++++-------- monitor/btcscanner/btc_scanner.go | 10 ++++-- monitor/monitor.go | 3 ++ 4 files changed, 70 insertions(+), 19 deletions(-) diff --git a/cmd/vigilante/cmd/monitor.go b/cmd/vigilante/cmd/monitor.go index 2b66c15b..b4ac8283 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "github.com/babylonlabs-io/vigilante/netparams" bbnqccfg "github.com/babylonlabs-io/babylon/client/config" bbnqc "github.com/babylonlabs-io/babylon/client/query" @@ -80,8 +81,28 @@ func GetMonitorCmd() *cobra.Command { // register monitor metrics monitorMetrics := metrics.NewMonitorMetrics() + // create the chain notifier + btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams) + if err != nil { + panic(fmt.Errorf("failed to get BTC net params: %w", err)) + } + btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) // todo(lazar955): check if we should use real cache + if err != nil { + panic(fmt.Errorf("failed to initialize notifier: %w", err)) + } + // create monitor - vigilanteMonitor, err = monitor.New(&cfg.Monitor, &cfg.Common, rootLogger, genesisInfo, bbnQueryClient, btcClient, monitorMetrics) + vigilanteMonitor, err = monitor.New( + &cfg.Monitor, + &cfg.Common, + rootLogger, + genesisInfo, + bbnQueryClient, + btcClient, + btcNotifier, + monitorMetrics, + ) if err != nil { panic(fmt.Errorf("failed to create vigilante monitor: %w", err)) } diff --git a/monitor/btcscanner/block_handler.go b/monitor/btcscanner/block_handler.go index 7054f9a4..e17588f8 100644 --- a/monitor/btcscanner/block_handler.go +++ b/monitor/btcscanner/block_handler.go @@ -3,38 +3,61 @@ package btcscanner import ( "errors" "fmt" - - "github.com/babylonlabs-io/vigilante/types" + "github.com/btcsuite/btcd/wire" ) // blockEventHandler handles connected and disconnected blocks from the BTC client. func (bs *BtcScanner) blockEventHandler() { defer bs.wg.Done() + if err := bs.btcNotifier.Start(); err != nil { + bs.logger.Errorf("Failed starting notifier") + return + } + + blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil) + if err != nil { + bs.logger.Errorf("Failed registering block epoch notifier") + return + } + + defer blockNotifier.Cancel() + for { select { case <-bs.quit: bs.BtcClient.Stop() return - case event, open := <-bs.BtcClient.BlockEventChan(): + case epoch, open := <-blockNotifier.Epochs: if !open { bs.logger.Errorf("Block event channel is closed") return // channel closed } - if event.EventType == types.BlockConnected { - err := bs.handleConnectedBlocks(event) - if err != nil { + + tip := bs.UnconfirmedBlockCache.Tip() + + // Determine if a reorg happened to we know which flow to continue + // if the new block has the same height but a different hash. + reorg := false + if tip != nil { + if epoch.Height < tip.Height || + (epoch.Height == tip.Height && epoch.BlockHeader.BlockHash() != tip.Header.BlockHash()) { + reorg = true + } + } + + if !reorg { + if err := bs.handleConnectedBlocks(epoch.BlockHeader); err != nil { bs.logger.Warnf("failed to handle a connected block at height %d: %s, "+ - "need to restart the bootstrapping process", event.Height, err.Error()) + "need to restart the bootstrapping process", epoch.Height, err.Error()) if bs.Synced.Swap(false) { bs.Bootstrap() } } - } else if event.EventType == types.BlockDisconnected { - err := bs.handleDisconnectedBlocks(event) - if err != nil { + } else { + if err := bs.handleDisconnectedBlocks(epoch.BlockHeader); err != nil { bs.logger.Warnf("failed to handle a disconnected block at height %d: %s,"+ - "need to restart the bootstrapping process", event.Height, err.Error()) + "need to restart the bootstrapping process", epoch.Height, err.Error()) if bs.Synced.Swap(false) { bs.Bootstrap() } @@ -46,13 +69,13 @@ func (bs *BtcScanner) blockEventHandler() { // handleConnectedBlocks handles connected blocks from the BTC client // if new confirmed blocks are found, send them through the channel -func (bs *BtcScanner) handleConnectedBlocks(event *types.BlockEvent) error { +func (bs *BtcScanner) handleConnectedBlocks(header *wire.BlockHeader) error { if !bs.Synced.Load() { return errors.New("the btc scanner is not synced") } // get the block from hash - blockHash := event.Header.BlockHash() + blockHash := header.BlockHash() ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash) if err != nil { // failing to request the block, which means a bug @@ -96,7 +119,7 @@ func (bs *BtcScanner) handleConnectedBlocks(event *types.BlockEvent) error { } // handleDisconnectedBlocks handles disconnected blocks from the BTC client. -func (bs *BtcScanner) handleDisconnectedBlocks(event *types.BlockEvent) error { +func (bs *BtcScanner) handleDisconnectedBlocks(header *wire.BlockHeader) error { // get cache tip cacheTip := bs.UnconfirmedBlockCache.Tip() if cacheTip == nil { @@ -104,7 +127,7 @@ func (bs *BtcScanner) handleDisconnectedBlocks(event *types.BlockEvent) error { } // if the block to be disconnected is not the tip of the cache, then the cache is not up-to-date, - if event.Header.BlockHash() != cacheTip.BlockHash() { + if header.BlockHash() != cacheTip.BlockHash() { return errors.New("cache is out-of-sync") } diff --git a/monitor/btcscanner/btc_scanner.go b/monitor/btcscanner/btc_scanner.go index 5ed6ebe4..bb4f1031 100644 --- a/monitor/btcscanner/btc_scanner.go +++ b/monitor/btcscanner/btc_scanner.go @@ -2,6 +2,7 @@ package btcscanner import ( "fmt" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "sync" "github.com/babylonlabs-io/babylon/btctxformatter" @@ -19,7 +20,8 @@ type BtcScanner struct { logger *zap.SugaredLogger // connect to BTC node - BtcClient btcclient.BTCClient + BtcClient btcclient.BTCClient + btcNotifier notifier.ChainNotifier // the BTC height the scanner starts BaseHeight uint64 @@ -49,7 +51,8 @@ func New( monitorCfg *config.MonitorConfig, parentLogger *zap.Logger, btcClient btcclient.BTCClient, - btclightclientBaseHeight uint64, + btcNotifier notifier.ChainNotifier, + btcLightClientBaseHeight uint64, checkpointTag []byte, ) (*BtcScanner, error) { headersChan := make(chan *wire.BlockHeader, monitorCfg.BtcBlockBufferSize) @@ -64,7 +67,8 @@ func New( return &BtcScanner{ logger: parentLogger.With(zap.String("module", "btcscanner")).Sugar(), BtcClient: btcClient, - BaseHeight: btclightclientBaseHeight, + btcNotifier: btcNotifier, + BaseHeight: btcLightClientBaseHeight, K: monitorCfg.BtcConfirmationDepth, ckptCache: ckptCache, UnconfirmedBlockCache: unconfirmedBlockCache, diff --git a/monitor/monitor.go b/monitor/monitor.go index e3fd7fff..e9199256 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -3,6 +3,7 @@ package monitor import ( "encoding/hex" "fmt" + notifier "github.com/lightningnetwork/lnd/chainntnfs" "sort" "sync" @@ -52,6 +53,7 @@ func New( genesisInfo *types.GenesisInfo, bbnQueryClient BabylonQueryClient, btcClient btcclient.BTCClient, + btcNotifier notifier.ChainNotifier, monitorMetrics *metrics.MonitorMetrics, ) (*Monitor, error) { logger := parentLogger.With(zap.String("module", "monitor")) @@ -64,6 +66,7 @@ func New( cfg, logger, btcClient, + btcNotifier, genesisInfo.GetBaseBTCHeight(), checkpointTagBytes, ) From 55c846d6e9e5fce5d6b4895f6e1bbf92a43d10b0 Mon Sep 17 00:00:00 2001 From: lazar Date: Thu, 22 Aug 2024 17:06:20 +0200 Subject: [PATCH 03/24] use btc client without subscription --- cmd/vigilante/cmd/btcstaking_tracker.go | 8 ++------ cmd/vigilante/cmd/monitor.go | 8 +------- cmd/vigilante/cmd/reporter.go | 2 +- e2etest/test_manager.go | 2 +- go.mod | 8 ++++++++ go.sum | 6 ++++++ monitor/btcscanner/btc_scanner.go | 2 -- reporter/bootstrapping.go | 2 +- 8 files changed, 20 insertions(+), 18 deletions(-) diff --git a/cmd/vigilante/cmd/btcstaking_tracker.go b/cmd/vigilante/cmd/btcstaking_tracker.go index f11bf804..8d4cbabd 100644 --- a/cmd/vigilante/cmd/btcstaking_tracker.go +++ b/cmd/vigilante/cmd/btcstaking_tracker.go @@ -61,12 +61,8 @@ func GetBTCStakingTracker() *cobra.Command { // create BTC client and connect to BTC server // Note that monitor needs to subscribe to new BTC blocks - btcClient, err := btcclient.NewWithBlockSubscriber( - &cfg.BTC, - cfg.Common.RetrySleepTime, - cfg.Common.MaxRetrySleepTime, - rootLogger, - ) + btcClient, err := btcclient.NewWallet(&cfg.BTC, rootLogger) + if err != nil { panic(fmt.Errorf("failed to open BTC client: %w", err)) } diff --git a/cmd/vigilante/cmd/monitor.go b/cmd/vigilante/cmd/monitor.go index b4ac8283..02fd3cbf 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -63,13 +63,7 @@ func GetMonitorCmd() *cobra.Command { } // create BTC client and connect to BTC server - // Note that monitor needs to subscribe to new BTC blocks - btcClient, err = btcclient.NewWithBlockSubscriber( - &cfg.BTC, - cfg.Common.RetrySleepTime, - cfg.Common.MaxRetrySleepTime, - rootLogger, - ) + btcClient, err = btcclient.NewWallet(&cfg.BTC, rootLogger) if err != nil { panic(fmt.Errorf("failed to open BTC client: %w", err)) } diff --git a/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index aec059cc..25d0eea5 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -49,7 +49,7 @@ func GetReporterCmd() *cobra.Command { // create BTC client and connect to BTC server // Note that vigilant reporter needs to subscribe to new BTC blocks - btcClient, err = btcclient.NewWithBlockSubscriber(&cfg.BTC, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, rootLogger) + btcClient, err = btcclient.NewWallet(&cfg.BTC, rootLogger) if err != nil { panic(fmt.Errorf("failed to open BTC client: %w", err)) } diff --git a/e2etest/test_manager.go b/e2etest/test_manager.go index 7a82624e..e7e2b003 100644 --- a/e2etest/test_manager.go +++ b/e2etest/test_manager.go @@ -65,7 +65,7 @@ type TestManager struct { } func initBTCClientWithSubscriber(t *testing.T, cfg *config.Config) *btcclient.Client { - client, err := btcclient.NewWithBlockSubscriber(&cfg.BTC, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, zap.NewNop()) + client, err := btcclient.NewWallet(&cfg.BTC, zap.NewNop()) require.NoError(t, err) // let's wait until chain rpc becomes available diff --git a/go.mod b/go.mod index 7456b18f..da6cfd56 100644 --- a/go.mod +++ b/go.mod @@ -68,6 +68,7 @@ require ( github.com/DataDog/zstd v1.5.5 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/andybalholm/brotli v1.0.5 // indirect github.com/aws/aws-sdk-go v1.44.312 // indirect @@ -75,7 +76,11 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect @@ -137,6 +142,7 @@ require ( github.com/fergusstrange/embedded-postgres v1.10.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect @@ -209,6 +215,7 @@ require ( github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect github.com/lightninglabs/neutrino v0.15.0 // indirect github.com/lightninglabs/neutrino/cache v1.1.1 // indirect + github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 // indirect github.com/lightningnetwork/lnd/clock v1.1.0 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect github.com/lightningnetwork/lnd/kvdb v1.4.1 // indirect @@ -217,6 +224,7 @@ require ( github.com/lightningnetwork/lnd/tlv v1.1.0 // indirect github.com/lightningnetwork/lnd/tor v1.1.0 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect + github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index ffc3ea90..3d28ba36 100644 --- a/go.sum +++ b/go.sum @@ -303,16 +303,19 @@ github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13P github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M= github.com/btcsuite/btcd v0.22.0-beta.0.20220204213055-eaf0459ff879/go.mod h1:osu7EoKiL36UThEgzYPqdRaxeo0NU8VoXqgcnwpey0g= github.com/btcsuite/btcd v0.22.0-beta.0.20220207191057-4dc4ff7963b4/go.mod h1:7alexyj/lHlOtr2PJK7L/+HDJZpcGDn/pAU98r7DY08= +github.com/btcsuite/btcd v0.23.1/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.3/go.mod h1:0QJIIN1wwIXF/3G/m87gIwGniDMDQqjVn4SZgnFpsYY= github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A= github.com/btcsuite/btcd v0.24.2 h1:aLmxPguqxza+4ag8R1I2nnJjSu2iFn/kqtHTIImswcY= github.com/btcsuite/btcd v0.24.2/go.mod h1:5C8ChTkl5ejr3WHj8tkQSCmydiMEPB0ZhQhehpq7Dgg= github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA= +github.com/btcsuite/btcd/btcec/v2 v2.1.1/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE= github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.0.0/go.mod h1:Uoxwv0pqYWhD//tfTiipkxNfdhG9UrLwaeswfjfdF0A= github.com/btcsuite/btcd/btcutil v1.1.0/go.mod h1:5OapHB7A2hBBWLm48mmw4MOHNJCcUBTwmWH/0Jn8VHE= +github.com/btcsuite/btcd/btcutil v1.1.1/go.mod h1:nbKlBMNm9FGsdvKvu0essceubPiAcI57pYBNnsLAa34= github.com/btcsuite/btcd/btcutil v1.1.5 h1:+wER79R5670vs/ZusMTF1yTcRYE5GUsFbdjdisflzM8= github.com/btcsuite/btcd/btcutil v1.1.5/go.mod h1:PSZZ4UitpLBWzxGd5VGOrLnmOjtPP/a6HaFo12zMs00= github.com/btcsuite/btcd/btcutil/psbt v1.1.8 h1:4voqtT8UppT7nmKQkXV+T9K8UyQjKOn2z/ycpmJK8wg= @@ -330,6 +333,7 @@ github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGH github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2/go.mod h1:Zpk/LOb2sKqwP2lmHjaZT9AdaKsHPSbNLm2Uql5IQ/0= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 h1:BtEN5Empw62/RVnZ0VcJaVtVlBijnLlJY+dwjAye2Bg= github.com/btcsuite/btcwallet/wallet/txrules v1.2.0/go.mod h1:AtkqiL7ccKWxuLYtZm8Bu8G6q82w4yIZdgq6riy60z0= +github.com/btcsuite/btcwallet/wallet/txsizes v1.2.2/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 h1:PszOub7iXVYbtGybym5TGCp9Dv1h1iX4rIC3HICZGLg= github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3/go.mod h1:q08Rms52VyWyXcp5zDc4tdFRKkFgNsMQrv3/LvE1448= github.com/btcsuite/btcwallet/walletdb v1.3.5/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= @@ -339,6 +343,7 @@ github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2c github.com/btcsuite/btcwallet/wtxmgr v1.5.0/go.mod h1:TQVDhFxseiGtZwEPvLgtfyxuNUDsIdaJdshvWzR0HJ4= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc= github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= @@ -1025,6 +1030,7 @@ github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5Of github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw= github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY= +github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= diff --git a/monitor/btcscanner/btc_scanner.go b/monitor/btcscanner/btc_scanner.go index bb4f1031..a102dbb7 100644 --- a/monitor/btcscanner/btc_scanner.go +++ b/monitor/btcscanner/btc_scanner.go @@ -91,8 +91,6 @@ func (bs *BtcScanner) Start() { // the bootstrapping should not block the main thread go bs.Bootstrap() - bs.BtcClient.MustSubscribeBlocks() - bs.Started.Store(true) bs.logger.Info("the BTC scanner is started") diff --git a/reporter/bootstrapping.go b/reporter/bootstrapping.go index 5e45c678..4f45d310 100644 --- a/reporter/bootstrapping.go +++ b/reporter/bootstrapping.go @@ -84,7 +84,7 @@ func (r *Reporter) bootstrap(skipBlockSubscription bool) error { // Subscribe new blocks right after initialising BTC cache, in order to ensure subscribed blocks and cached blocks do not have overlap. // Otherwise, if we subscribe too early, then they will have overlap, leading to duplicated header/ckpt submissions. if !skipBlockSubscription { - r.btcClient.MustSubscribeBlocks() + //r.btcClient.MustSubscribeBlocks() // todo(lazar): check if we need to handle this } consistencyInfo, err := r.checkConsistency() From eaa9900f82328697ed96d3f4e36edc4bf2dbd0a2 Mon Sep 17 00:00:00 2001 From: lazar Date: Thu, 22 Aug 2024 17:31:42 +0200 Subject: [PATCH 04/24] remove zmq impl --- btcclient/client.go | 14 --- btcclient/client_block_subscriber.go | 139 -------------------- btcclient/interface.go | 2 - go.mod | 1 - go.sum | 2 - zmq/client.go | 129 ------------------- zmq/subscribe.go | 182 --------------------------- 7 files changed, 469 deletions(-) delete mode 100644 btcclient/client_block_subscriber.go delete mode 100644 zmq/client.go delete mode 100644 zmq/subscribe.go diff --git a/btcclient/client.go b/btcclient/client.go index 4cd3933f..833b41bc 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -16,7 +16,6 @@ import ( "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/types" - "github.com/babylonlabs-io/vigilante/zmq" ) var _ BTCClient = &Client{} @@ -25,7 +24,6 @@ var _ BTCClient = &Client{} // for information regarding the current best block chain. type Client struct { *rpcclient.Client - zmqClient *zmq.Client Params *chaincfg.Params Cfg *config.BTCConfig @@ -54,16 +52,4 @@ func (c *Client) GetTipBlockVerbose() (*btcjson.GetBlockVerboseResult, error) { func (c *Client) Stop() { c.Shutdown() - // NewWallet will create a client with nil blockEventChan, - // while NewWithBlockSubscriber will have a non-nil one, so - // we need to check here - if c.blockEventChan != nil { - close(c.blockEventChan) - } - - if c.zmqClient != nil { - if err := c.zmqClient.Close(); err != nil { - c.logger.Debug(err) - } - } } diff --git a/btcclient/client_block_subscriber.go b/btcclient/client_block_subscriber.go deleted file mode 100644 index c3245158..00000000 --- a/btcclient/client_block_subscriber.go +++ /dev/null @@ -1,139 +0,0 @@ -package btcclient - -import ( - "fmt" - "time" - - "github.com/babylonlabs-io/babylon/types/retry" - "github.com/btcsuite/btcd/btcutil" - "go.uber.org/zap" - - "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/netparams" - "github.com/babylonlabs-io/vigilante/types" - "github.com/babylonlabs-io/vigilante/zmq" - - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" -) - -// NewWithBlockSubscriber creates a new BTC client that subscribes to newly connected/disconnected blocks -// used by vigilant reporter -func NewWithBlockSubscriber(cfg *config.BTCConfig, retrySleepTime, maxRetrySleepTime time.Duration, parentLogger *zap.Logger) (*Client, error) { - client := &Client{} - params, err := netparams.GetBTCParams(cfg.NetParams) - if err != nil { - return nil, err - } - client.blockEventChan = make(chan *types.BlockEvent, 10000) // TODO: parameterise buffer size - client.Cfg = cfg - client.Params = params - logger := parentLogger.With(zap.String("module", "btcclient")) - client.logger = logger.Sugar() - - client.retrySleepTime = retrySleepTime - client.maxRetrySleepTime = maxRetrySleepTime - - switch cfg.BtcBackend { - case types.Bitcoind: - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg := &rpcclient.ConnConfig{ - Host: cfg.Endpoint, - HTTPPostMode: true, - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - } - - rpcClient, err := rpcclient.New(connCfg, nil) - if err != nil { - return nil, err - } - - zmqClient, err := zmq.New(logger, cfg.ZmqSeqEndpoint, client.blockEventChan, rpcClient) - if err != nil { - return nil, err - } - - client.zmqClient = zmqClient - client.Client = rpcClient - case types.Btcd: - notificationHandlers := rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - client.logger.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - client.blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - client.logger.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - client.blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - } - - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg := &rpcclient.ConnConfig{ - Host: cfg.Endpoint, - Endpoint: "ws", // websocket - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - Certificates: cfg.ReadCAFile(), - } - - rpcClient, err := rpcclient.New(connCfg, ¬ificationHandlers) - if err != nil { - return nil, err - } - - // ensure we are using btcd as Bitcoin node, since Websocket-based subscriber is only available in btcd - backend, err := rpcClient.BackendVersion() - if err != nil { - return nil, fmt.Errorf("failed to get BTC backend: %v", err) - } - if backend != rpcclient.BtcdPost2401 { - return nil, fmt.Errorf("websocket is only supported by btcd, but got %v", backend) - } - - client.Client = rpcClient - } - - client.logger.Info("Successfully created the BTC client and connected to the BTC server") - - return client, nil -} - -func (c *Client) subscribeBlocksByWebSocket() error { - if err := c.NotifyBlocks(); err != nil { - return err - } - c.logger.Info("Successfully subscribed to newly connected/disconnected blocks via WebSocket") - return nil -} - -func (c *Client) mustSubscribeBlocksByWebSocket() { - if err := retry.Do(c.retrySleepTime, c.maxRetrySleepTime, func() error { - return c.subscribeBlocksByWebSocket() - }); err != nil { - panic(err) - } -} - -func (c *Client) mustSubscribeBlocksByZmq() { - if err := c.zmqClient.SubscribeSequence(); err != nil { - panic(err) - } -} - -func (c *Client) MustSubscribeBlocks() { - switch c.Cfg.BtcBackend { - case types.Btcd: - c.mustSubscribeBlocksByWebSocket() - case types.Bitcoind: - c.mustSubscribeBlocksByZmq() - } -} - -func (c *Client) BlockEventChan() <-chan *types.BlockEvent { - return c.blockEventChan -} diff --git a/btcclient/interface.go b/btcclient/interface.go index d3021412..52648acc 100644 --- a/btcclient/interface.go +++ b/btcclient/interface.go @@ -14,8 +14,6 @@ import ( type BTCClient interface { Stop() WaitForShutdown() - MustSubscribeBlocks() - BlockEventChan() <-chan *types.BlockEvent GetBestBlock() (*chainhash.Hash, uint64, error) GetBlockByHash(blockHash *chainhash.Hash) (*types.IndexedBlock, *wire.MsgBlock, error) FindTailBlocksByHeight(height uint64) ([]*types.IndexedBlock, error) diff --git a/go.mod b/go.mod index da6cfd56..fb2d502d 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/jsternberg/zap-logfmt v1.3.0 github.com/lightningnetwork/lnd v0.16.4-beta.rc1 github.com/ory/dockertest/v3 v3.9.1 - github.com/pebbe/zmq4 v1.2.9 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 3d28ba36..8ff2ee2b 100644 --- a/go.sum +++ b/go.sum @@ -1166,8 +1166,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pebbe/zmq4 v1.2.9 h1:JlHcdgq6zpppNR1tH0wXJq0XK03pRUc4lBlHTD7aj/4= -github.com/pebbe/zmq4 v1.2.9/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= diff --git a/zmq/client.go b/zmq/client.go deleted file mode 100644 index 51184f42..00000000 --- a/zmq/client.go +++ /dev/null @@ -1,129 +0,0 @@ -// Package zmq reference is taken from https://github.com/joakimofv/go-bitcoindclient which is a -// go wrapper around official zmq package https://github.com/pebbe/zmq4 -package zmq - -import ( - "errors" - "sync" - "sync/atomic" - - "github.com/babylonlabs-io/vigilante/types" - "github.com/btcsuite/btcd/rpcclient" - "github.com/pebbe/zmq4" - "go.uber.org/zap" -) - -var ( - ErrSubscribeDisabled = errors.New("subscribe disabled (ZmqEndpoint was not set)") - ErrSubscribeExited = errors.New("subscription backend has exited") - ErrSubscriptionAlreadyActive = errors.New("active subscription already exists") -) - -// Client is a client that provides methods for interacting with zmq4. -// Must be created with New and destroyed with Close. -// Clients are safe for concurrent use by multiple goroutines. -type Client struct { - rpcClient *rpcclient.Client - logger *zap.SugaredLogger - closed int32 // Set atomically. - wg sync.WaitGroup - quit chan struct{} - - zmqEndpoint string - blockEventChan chan *types.BlockEvent - - // ZMQ subscription related things. - zctx *zmq4.Context - zsub *zmq4.Socket - subs subscriptions - // subs.zfront --> zback is used like a channel to send messages to the zmqHandler goroutine. - // Have to use zmq4 sockets in place of native channels for communication from - // other functions to the goroutine, since it is constantly waiting on the zsub socket, - // it can't select on a channel at the same time but can poll on multiple sockets. - zback *zmq4.Socket -} - -// New returns an initiated client, or an error. -func New(parentLogger *zap.Logger, zmqEndpoint string, blockEventChan chan *types.BlockEvent, rpcClient *rpcclient.Client) (*Client, error) { - var ( - zctx *zmq4.Context - zsub *zmq4.Socket - zback *zmq4.Socket - err error - c = &Client{ - quit: make(chan struct{}), - rpcClient: rpcClient, - zmqEndpoint: zmqEndpoint, - logger: parentLogger.With(zap.String("module", "zmq")).Sugar(), - } - ) - - // ZMQ Subscribe. - zctx, err = zmq4.NewContext() - if err != nil { - return nil, err - } - - zsub, err = zctx.NewSocket(zmq4.SUB) - if err != nil { - return nil, err - } - if err = zsub.Connect(zmqEndpoint); err != nil { - return nil, err - } - - zback, err = zctx.NewSocket(zmq4.PAIR) - if err != nil { - return nil, err - } - if err = zback.Bind("inproc://channel"); err != nil { - return nil, err - } - - zfront, err := zctx.NewSocket(zmq4.PAIR) - if err != nil { - return nil, err - } - if err = zfront.Connect("inproc://channel"); err != nil { - return nil, err - } - - c.zctx = zctx - c.zsub = zsub - c.subs.exited = make(chan struct{}) - c.subs.zfront = zfront - c.zback = zback - c.blockEventChan = blockEventChan - - c.wg.Add(1) - go c.zmqHandler() - - return c, nil -} - -// Close terminates the client and releases resources. -func (c *Client) Close() (err error) { - if !atomic.CompareAndSwapInt32(&c.closed, 0, 1) { - return errors.New("client already closed") - } - if c.zctx != nil { - c.zctx.SetRetryAfterEINTR(false) - c.subs.Lock() - select { - case <-c.subs.exited: - default: - if _, err = c.subs.zfront.SendMessage("term"); err != nil { - return err - } - } - c.subs.Unlock() - <-c.subs.exited - err = c.zctx.Term() - if err != nil { - return err - } - } - close(c.quit) - c.wg.Wait() - return nil -} diff --git a/zmq/subscribe.go b/zmq/subscribe.go deleted file mode 100644 index 2df13ba6..00000000 --- a/zmq/subscribe.go +++ /dev/null @@ -1,182 +0,0 @@ -package zmq - -import ( - "encoding/hex" - "sync" - "time" - - "github.com/babylonlabs-io/vigilante/types" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" - zmq "github.com/pebbe/zmq4" -) - -// SequenceMsg is a subscription event coming from a "sequence" ZMQ message. -type SequenceMsg struct { - Hash [32]byte // use encoding/hex.EncodeToString() to get it into the RPC method string format. - Event types.EventType -} - -type subscriptions struct { - sync.RWMutex - - exited chan struct{} - zfront *zmq.Socket - latestEvent time.Time - active bool -} - -// SubscribeSequence subscribes to the ZMQ "sequence" messages as SequenceMsg items pushed onto the channel. -// Call cancel to cancel the subscription and let the client release the resources. The channel is closed -// when the subscription is canceled or when the client is closed. -func (c *Client) SubscribeSequence() (err error) { - if c.zsub == nil { - err = ErrSubscribeDisabled - return - } - c.subs.Lock() - select { - case <-c.subs.exited: - err = ErrSubscribeExited - c.subs.Unlock() - return - default: - } - - if c.subs.active { - err = ErrSubscriptionAlreadyActive - return - } - - _, err = c.subs.zfront.SendMessage("subscribe", "sequence") - if err != nil { - c.subs.Unlock() - return - } - c.subs.active = true - - c.subs.Unlock() - return -} - -func (c *Client) zmqHandler() { - defer c.wg.Done() - defer func(zsub *zmq.Socket) { - err := zsub.Close() - if err != nil { - c.logger.Errorf("Error closing ZMQ socket: %v", err) - } - }(c.zsub) - defer func(zback *zmq.Socket) { - err := zback.Close() - if err != nil { - c.logger.Errorf("Error closing ZMQ socket: %v", err) - } - }(c.zback) - - poller := zmq.NewPoller() - poller.Add(c.zsub, zmq.POLLIN) - poller.Add(c.zback, zmq.POLLIN) -OUTER: - for { - // Wait forever until a message can be received or the context was cancelled. - polled, err := poller.Poll(-1) - if err != nil { - break OUTER - } - - for _, p := range polled { - switch p.Socket { - case c.zsub: - msg, err := c.zsub.RecvMessage(0) - if err != nil { - break OUTER - } - c.subs.latestEvent = time.Now() - switch msg[0] { - case "sequence": - var sequenceMsg SequenceMsg - copy(sequenceMsg.Hash[:], msg[1]) - switch msg[1][32] { - case 'C': - sequenceMsg.Event = types.BlockConnected - case 'D': - sequenceMsg.Event = types.BlockDisconnected - default: - // not interested in other events - continue - } - - c.sendBlockEvent(sequenceMsg.Hash[:], sequenceMsg.Event) - } - - case c.zback: - msg, err := c.zback.RecvMessage(0) - if err != nil { - break OUTER - } - switch msg[0] { - case "subscribe": - if err := c.zsub.SetSubscribe(msg[1]); err != nil { - break OUTER - } - case "term": - break OUTER - } - } - } - } - - c.subs.Lock() - close(c.subs.exited) - err := c.subs.zfront.Close() - if err != nil { - c.logger.Errorf("Error closing zfront: %v", err) - return - } - // Close all subscriber channels. - if c.subs.active { - err = c.zsub.SetUnsubscribe("sequence") - if err != nil { - c.logger.Errorf("Error unsubscribing from sequence: %v", err) - return - } - } - - c.subs.Unlock() -} - -func (c *Client) sendBlockEvent(hash []byte, event types.EventType) { - blockHashStr := hex.EncodeToString(hash[:]) - blockHash, err := chainhash.NewHashFromStr(blockHashStr) - if err != nil { - c.logger.Errorf("Failed to parse block hash %v: %v", blockHashStr, err) - panic(err) - } - - c.logger.Infof("Received zmq sequence message for block %v", blockHashStr) - - ib, _, err := c.getBlockByHash(blockHash) - if err != nil { - c.logger.Errorf("Failed to get block %v from BTC client: %v", blockHash, err) - panic(err) - } - - c.blockEventChan <- types.NewBlockEvent(event, ib.Height, ib.Header) -} - -func (c *Client) getBlockByHash(blockHash *chainhash.Hash) (*types.IndexedBlock, *wire.MsgBlock, error) { - // TODO: ZMQ should not use BTC/RPC client, modify BlockEvent to include block hash - blockInfo, err := c.rpcClient.GetBlockVerbose(blockHash) - if err != nil { - return nil, nil, err - } - - mBlock, err := c.rpcClient.GetBlock(blockHash) - if err != nil { - return nil, nil, err - } - - btcTxs := types.GetWrappedTxs(mBlock) - return types.NewIndexedBlock(int32(blockInfo.Height), &mBlock.Header, btcTxs), mBlock, nil -} From 8575dd15c0f91386ff4a6a33efa45ef242748427 Mon Sep 17 00:00:00 2001 From: lazar Date: Thu, 22 Aug 2024 17:44:01 +0200 Subject: [PATCH 05/24] update mocks --- testutil/mocks/btcclient.go | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/testutil/mocks/btcclient.go b/testutil/mocks/btcclient.go index 1bbdb6b9..1d80f7cc 100644 --- a/testutil/mocks/btcclient.go +++ b/testutil/mocks/btcclient.go @@ -40,20 +40,6 @@ func (m *MockBTCClient) EXPECT() *MockBTCClientMockRecorder { return m.recorder } -// BlockEventChan mocks base method. -func (m *MockBTCClient) BlockEventChan() <-chan *types.BlockEvent { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BlockEventChan") - ret0, _ := ret[0].(<-chan *types.BlockEvent) - return ret0 -} - -// BlockEventChan indicates an expected call of BlockEventChan. -func (mr *MockBTCClientMockRecorder) BlockEventChan() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BlockEventChan", reflect.TypeOf((*MockBTCClient)(nil).BlockEventChan)) -} - // FindTailBlocksByHeight mocks base method. func (m *MockBTCClient) FindTailBlocksByHeight(height uint64) ([]*types.IndexedBlock, error) { m.ctrl.T.Helper() @@ -162,18 +148,6 @@ func (mr *MockBTCClientMockRecorder) GetTxOut(txHash, index, mempool interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTxOut", reflect.TypeOf((*MockBTCClient)(nil).GetTxOut), txHash, index, mempool) } -// MustSubscribeBlocks mocks base method. -func (m *MockBTCClient) MustSubscribeBlocks() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "MustSubscribeBlocks") -} - -// MustSubscribeBlocks indicates an expected call of MustSubscribeBlocks. -func (mr *MockBTCClientMockRecorder) MustSubscribeBlocks() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MustSubscribeBlocks", reflect.TypeOf((*MockBTCClient)(nil).MustSubscribeBlocks)) -} - // SendRawTransaction mocks base method. func (m *MockBTCClient) SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) { m.ctrl.T.Helper() From 998cee9868b68d5b70478f768d4355ab1bb541cc Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 23 Aug 2024 10:44:13 +0200 Subject: [PATCH 06/24] cleanup unused code --- btcclient/client.go | 8 ++------ btcclient/client_wallet.go | 12 ++++++------ btcclient/interface.go | 1 - btcclient/testutils.go | 25 ------------------------- testutil/mocks/btcclient.go | 15 --------------- 5 files changed, 8 insertions(+), 53 deletions(-) delete mode 100644 btcclient/testutils.go diff --git a/btcclient/client.go b/btcclient/client.go index 833b41bc..8da20dcd 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -15,7 +15,6 @@ import ( "go.uber.org/zap" "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/types" ) var _ BTCClient = &Client{} @@ -25,16 +24,13 @@ var _ BTCClient = &Client{} type Client struct { *rpcclient.Client - Params *chaincfg.Params - Cfg *config.BTCConfig + params *chaincfg.Params + cfg *config.BTCConfig logger *zap.SugaredLogger // retry attributes retrySleepTime time.Duration maxRetrySleepTime time.Duration - - // channel for notifying new BTC blocks to reporter - blockEventChan chan *types.BlockEvent } func (c *Client) GetTipBlockVerbose() (*btcjson.GetBlockVerboseResult, error) { diff --git a/btcclient/client_wallet.go b/btcclient/client_wallet.go index fe52e740..16c964bd 100644 --- a/btcclient/client_wallet.go +++ b/btcclient/client_wallet.go @@ -26,8 +26,8 @@ func NewWallet(cfg *config.BTCConfig, parentLogger *zap.Logger) (*Client, error) return nil, err } wallet := &Client{} - wallet.Cfg = cfg - wallet.Params = params + wallet.cfg = cfg + wallet.params = params wallet.logger = parentLogger.With(zap.String("module", "btcclient_wallet")).Sugar() connCfg := &rpcclient.ConnConfig{} @@ -69,15 +69,15 @@ func NewWallet(cfg *config.BTCConfig, parentLogger *zap.Logger) (*Client, error) } func (c *Client) GetWalletPass() string { - return c.Cfg.WalletPassword + return c.cfg.WalletPassword } func (c *Client) GetWalletLockTime() int64 { - return c.Cfg.WalletLockTime + return c.cfg.WalletLockTime } func (c *Client) GetNetParams() *chaincfg.Params { - net, err := netparams.GetBTCParams(c.Cfg.NetParams) + net, err := netparams.GetBTCParams(c.cfg.NetParams) if err != nil { panic(fmt.Errorf("failed to get BTC network params: %w", err)) } @@ -85,7 +85,7 @@ func (c *Client) GetNetParams() *chaincfg.Params { } func (c *Client) GetBTCConfig() *config.BTCConfig { - return c.Cfg + return c.cfg } func (c *Client) ListUnspent() ([]btcjson.ListUnspentResult, error) { diff --git a/btcclient/interface.go b/btcclient/interface.go index 52648acc..71b9bca5 100644 --- a/btcclient/interface.go +++ b/btcclient/interface.go @@ -35,7 +35,6 @@ type BTCWallet interface { SendRawTransaction(tx *wire.MsgTx, allowHighFees bool) (*chainhash.Hash, error) GetRawChangeAddress(account string) (btcutil.Address, error) WalletPassphrase(passphrase string, timeoutSecs int64) error - DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) GetHighUTXOAndSum() (*btcjson.ListUnspentResult, float64, error) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) diff --git a/btcclient/testutils.go b/btcclient/testutils.go deleted file mode 100644 index c0941e58..00000000 --- a/btcclient/testutils.go +++ /dev/null @@ -1,25 +0,0 @@ -package btcclient - -import ( - "time" - - "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/netparams" - "github.com/babylonlabs-io/vigilante/types" - "github.com/btcsuite/btcd/rpcclient" -) - -func NewTestClientWithWsSubscriber(rpcClient *rpcclient.Client, cfg *config.BTCConfig, retrySleepTime time.Duration, maxRetrySleepTime time.Duration, blockEventChan chan *types.BlockEvent) (*Client, error) { - net, err := netparams.GetBTCParams(cfg.NetParams) - if err != nil { - return nil, err - } - return &Client{ - Client: rpcClient, - Params: net, - Cfg: cfg, - retrySleepTime: retrySleepTime, - maxRetrySleepTime: maxRetrySleepTime, - blockEventChan: blockEventChan, - }, nil -} diff --git a/testutil/mocks/btcclient.go b/testutil/mocks/btcclient.go index 1d80f7cc..76581b6c 100644 --- a/testutil/mocks/btcclient.go +++ b/testutil/mocks/btcclient.go @@ -210,21 +210,6 @@ func (m *MockBTCWallet) EXPECT() *MockBTCWalletMockRecorder { return m.recorder } -// DumpPrivKey mocks base method. -func (m *MockBTCWallet) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DumpPrivKey", address) - ret0, _ := ret[0].(*btcutil.WIF) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// DumpPrivKey indicates an expected call of DumpPrivKey. -func (mr *MockBTCWalletMockRecorder) DumpPrivKey(address interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DumpPrivKey", reflect.TypeOf((*MockBTCWallet)(nil).DumpPrivKey), address) -} - // FundRawTransaction mocks base method. func (m *MockBTCWallet) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) { m.ctrl.T.Helper() From 376bc3acacc824c66a36e1f001954954ee20a8b1 Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 23 Aug 2024 11:34:11 +0200 Subject: [PATCH 07/24] cleanup --- cmd/vigilante/cmd/monitor.go | 2 +- cmd/vigilante/cmd/reporter.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/vigilante/cmd/monitor.go b/cmd/vigilante/cmd/monitor.go index 02fd3cbf..6fba578a 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -81,7 +81,7 @@ func GetMonitorCmd() *cobra.Command { panic(fmt.Errorf("failed to get BTC net params: %w", err)) } btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") - btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) // todo(lazar955): check if we should use real cache + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) if err != nil { panic(fmt.Errorf("failed to initialize notifier: %w", err)) } diff --git a/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index 25d0eea5..3c0e83b6 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -69,7 +69,7 @@ func GetReporterCmd() *cobra.Command { panic(fmt.Errorf("failed to get BTC net params: %w", err)) } btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") - btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) // todo Lazar check if we should use concrete cache here? + btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) if err != nil { panic(fmt.Errorf("failed to initialize notifier: %w", err)) } From 93f04e3685769dc320a51b57a6cf8277f6389a73 Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 23 Aug 2024 11:46:15 +0200 Subject: [PATCH 08/24] fix test --- monitor/btcscanner/btc_scanner_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/monitor/btcscanner/btc_scanner_test.go b/monitor/btcscanner/btc_scanner_test.go index 74b8f195..5a5c302b 100644 --- a/monitor/btcscanner/btc_scanner_test.go +++ b/monitor/btcscanner/btc_scanner_test.go @@ -30,7 +30,6 @@ func FuzzBootStrap(f *testing.F) { ctl := gomock.NewController(t) mockBtcClient := mocks.NewMockBTCClient(ctl) confirmedBlocks := chainIndexedBlocks[:numBlocks-k] - mockBtcClient.EXPECT().MustSubscribeBlocks().Return().AnyTimes() mockBtcClient.EXPECT().GetBestBlock().Return(nil, uint64(bestHeight), nil) for i := 0; i < int(numBlocks); i++ { mockBtcClient.EXPECT().GetBlockByHeight(gomock.Eq(uint64(chainIndexedBlocks[i].Height))). From 3d19173c4dd64508cd540fa081e8e3008402f321 Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 23 Aug 2024 11:57:22 +0200 Subject: [PATCH 09/24] build flag --- e2etest/reporter_e2e_test.go | 3 +++ reporter/bootstrapping.go | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/e2etest/reporter_e2e_test.go b/e2etest/reporter_e2e_test.go index f86b90f6..9fec7d97 100644 --- a/e2etest/reporter_e2e_test.go +++ b/e2etest/reporter_e2e_test.go @@ -1,3 +1,6 @@ +//go:build e2e +// +build e2e + package e2etest import ( diff --git a/reporter/bootstrapping.go b/reporter/bootstrapping.go index 4f45d310..6dab260f 100644 --- a/reporter/bootstrapping.go +++ b/reporter/bootstrapping.go @@ -83,9 +83,9 @@ func (r *Reporter) bootstrap(skipBlockSubscription bool) error { // Subscribe new blocks right after initialising BTC cache, in order to ensure subscribed blocks and cached blocks do not have overlap. // Otherwise, if we subscribe too early, then they will have overlap, leading to duplicated header/ckpt submissions. - if !skipBlockSubscription { - //r.btcClient.MustSubscribeBlocks() // todo(lazar): check if we need to handle this - } + //if !skipBlockSubscription { + // //r.btcClient.MustSubscribeBlocks() // todo(lazar): check if we need to handle this + //} consistencyInfo, err := r.checkConsistency() From e3eb8b7de8722c84d9ca30a07ad2dce1bb7f5406 Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 23 Aug 2024 15:07:31 +0200 Subject: [PATCH 10/24] rm commented out code --- reporter/bootstrapping.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/reporter/bootstrapping.go b/reporter/bootstrapping.go index 6dab260f..551d9961 100644 --- a/reporter/bootstrapping.go +++ b/reporter/bootstrapping.go @@ -81,14 +81,7 @@ func (r *Reporter) bootstrap(skipBlockSubscription bool) error { } r.logger.Debugf("BTC cache size: %d", r.btcCache.Size()) - // Subscribe new blocks right after initialising BTC cache, in order to ensure subscribed blocks and cached blocks do not have overlap. - // Otherwise, if we subscribe too early, then they will have overlap, leading to duplicated header/ckpt submissions. - //if !skipBlockSubscription { - // //r.btcClient.MustSubscribeBlocks() // todo(lazar): check if we need to handle this - //} - consistencyInfo, err := r.checkConsistency() - if err != nil { return err } @@ -132,6 +125,7 @@ func (r *Reporter) bootstrap(skipBlockSubscription bool) error { } r.logger.Info("Successfully finished bootstrapping") + return nil } @@ -226,6 +220,7 @@ func (r *Reporter) initBTCCache() error { if err = r.btcCache.Init(ibs); err != nil { panic(err) } + return nil } @@ -316,5 +311,6 @@ func (r *Reporter) checkHeaderConsistency(consistencyCheckHeight uint64) error { err = fmt.Errorf("BTC main chain is inconsistent with BBN header chain: k-deep block in BBN header chain: %v", consistencyCheckHash) panic(err) } + return nil } From bdf4f9e0dc28c44ce6e4b9089123f77f1c8deddc Mon Sep 17 00:00:00 2001 From: lazar Date: Mon, 26 Aug 2024 13:13:43 +0200 Subject: [PATCH 11/24] pr comments refactor notifier init merge reporter block handles --- btcclient/notifier.go | 16 +++ cmd/vigilante/cmd/btcstaking_tracker.go | 10 +- cmd/vigilante/cmd/monitor.go | 11 +- cmd/vigilante/cmd/reporter.go | 11 +- reporter/block_handler.go | 173 +++++------------------- reporter/bootstrapping.go | 9 +- reporter/reporter.go | 17 ++- 7 files changed, 75 insertions(+), 172 deletions(-) diff --git a/btcclient/notifier.go b/btcclient/notifier.go index 4bc73c71..2ecc10ca 100644 --- a/btcclient/notifier.go +++ b/btcclient/notifier.go @@ -3,6 +3,7 @@ package btcclient import ( "encoding/hex" "fmt" + "github.com/babylonlabs-io/vigilante/netparams" "net" "os" "time" @@ -248,3 +249,18 @@ func NewNodeBackend( return nil, fmt.Errorf("unknown node backend: %v", cfg.ActiveNodeBackend) } } + +// NewNodeBackendWithParams creates a new NodeBackend by incorporating parameter retrieval and config conversion. +func NewNodeBackendWithParams(cfg config.BTCConfig, rawCert string) (*NodeBackend, error) { + btcParams, err := netparams.GetBTCParams(cfg.NetParams) + if err != nil { + return nil, fmt.Errorf("failed to get BTC net params: %w", err) + } + + btcNotifier, err := NewNodeBackend(CfgToBtcNodeBackendConfig(cfg, rawCert), btcParams, &EmptyHintCache{}) + if err != nil { + return nil, fmt.Errorf("failed to initialize notifier: %w", err) + } + + return btcNotifier, nil +} diff --git a/cmd/vigilante/cmd/btcstaking_tracker.go b/cmd/vigilante/cmd/btcstaking_tracker.go index 8d4cbabd..d4794170 100644 --- a/cmd/vigilante/cmd/btcstaking_tracker.go +++ b/cmd/vigilante/cmd/btcstaking_tracker.go @@ -8,7 +8,6 @@ import ( bst "github.com/babylonlabs-io/vigilante/btcstaking-tracker" "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/metrics" - "github.com/babylonlabs-io/vigilante/netparams" "github.com/babylonlabs-io/vigilante/rpcserver" "github.com/spf13/cobra" ) @@ -69,14 +68,9 @@ func GetBTCStakingTracker() *cobra.Command { // create BTC notifier // TODO: is it possible to merge BTC client and BTC notifier? - btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams) + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") if err != nil { - panic(fmt.Errorf("failed to get BTC parameter: %w", err)) - } - btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") // we will read certifcates from file - btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) - if err != nil { - panic(fmt.Errorf("failed to create btc chain notifier: %w", err)) + panic(err) } bsMetrics := metrics.NewBTCStakingTrackerMetrics() diff --git a/cmd/vigilante/cmd/monitor.go b/cmd/vigilante/cmd/monitor.go index 6fba578a..49259c50 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -2,8 +2,6 @@ package cmd import ( "fmt" - "github.com/babylonlabs-io/vigilante/netparams" - bbnqccfg "github.com/babylonlabs-io/babylon/client/config" bbnqc "github.com/babylonlabs-io/babylon/client/query" "github.com/spf13/cobra" @@ -76,14 +74,9 @@ func GetMonitorCmd() *cobra.Command { monitorMetrics := metrics.NewMonitorMetrics() // create the chain notifier - btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams) - if err != nil { - panic(fmt.Errorf("failed to get BTC net params: %w", err)) - } - btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") - btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") if err != nil { - panic(fmt.Errorf("failed to initialize notifier: %w", err)) + panic(err) } // create monitor diff --git a/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index 3c0e83b6..5c9a0b60 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -2,8 +2,6 @@ package cmd import ( "fmt" - "github.com/babylonlabs-io/vigilante/netparams" - bbnclient "github.com/babylonlabs-io/babylon/client/client" "github.com/spf13/cobra" @@ -64,14 +62,9 @@ func GetReporterCmd() *cobra.Command { reporterMetrics := metrics.NewReporterMetrics() // create the chain notifier - btcParams, err := netparams.GetBTCParams(cfg.BTC.NetParams) - if err != nil { - panic(fmt.Errorf("failed to get BTC net params: %w", err)) - } - btcCfg := btcclient.CfgToBtcNodeBackendConfig(cfg.BTC, "") - btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") if err != nil { - panic(fmt.Errorf("failed to initialize notifier: %w", err)) + panic(err) } // create reporter diff --git a/reporter/block_handler.go b/reporter/block_handler.go index dd9eff10..f14dea32 100644 --- a/reporter/block_handler.go +++ b/reporter/block_handler.go @@ -4,24 +4,14 @@ import ( "fmt" "github.com/babylonlabs-io/vigilante/types" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/chainntnfs" ) // blockEventHandler handles connected and disconnected blocks from the BTC client. -func (r *Reporter) blockEventHandler() { +func (r *Reporter) blockEventHandler(blockNotifier *chainntnfs.BlockEpochEvent) { defer r.wg.Done() quit := r.quitChan() - if err := r.btcNotifier.Start(); err != nil { - r.logger.Errorf("Failed starting notifier") - return - } - - blockNotifier, err := r.btcNotifier.RegisterBlockEpochNtfn(nil) - if err != nil { - r.logger.Errorf("Failed registering block epoch notifier") - return - } - defer blockNotifier.Cancel() for { @@ -32,28 +22,9 @@ func (r *Reporter) blockEventHandler() { return // channel closed } - tip := r.btcCache.Tip() - - // Determine if a reorg happened to we know which flow to continue - // if the new block has the same height but a different hash. - reorg := false - if tip != nil { - if epoch.Height < tip.Height || - (epoch.Height == tip.Height && epoch.BlockHeader.BlockHash() != tip.Header.BlockHash()) { - reorg = true - } - } - - var errorRequiringBootstrap error - if !reorg { - errorRequiringBootstrap = r.handleConnectedBlocks(epoch.Height, epoch.BlockHeader) - } else { - errorRequiringBootstrap = r.handleDisconnectedBlocks(epoch.BlockHeader) - } - - if errorRequiringBootstrap != nil { - r.logger.Warnf("Due to error in event processing: %v, bootstrap process need to be restarted", errorRequiringBootstrap) - r.bootstrapWithRetries(true) + if err := r.handleNewBlock(epoch.Height, epoch.BlockHeader); err != nil { + r.logger.Warnf("Due to error in event processing: %v, bootstrap process need to be restarted", err) + r.bootstrapWithRetries() } case <-quit: // We have been asked to stop @@ -62,16 +33,14 @@ func (r *Reporter) blockEventHandler() { } } -// handleConnectedBlocks handles connected blocks from the BTC client. -func (r *Reporter) handleConnectedBlocks(height int32, header *wire.BlockHeader) error { - // if the header is too early, ignore it - // NOTE: this might happen when bootstrapping is triggered after the reporter - // has subscribed to the BTC blocks - firstCacheBlock := r.btcCache.First() - if firstCacheBlock == nil { +// handleNewBlock processes a new block, checking if it connects to the cache or requires bootstrapping. +func (r *Reporter) handleNewBlock(height int32, header *wire.BlockHeader) error { + cacheTip := r.btcCache.Tip() + if cacheTip == nil { return fmt.Errorf("cache is empty, restart bootstrap process") } - if height < firstCacheBlock.Height { + + if cacheTip.Height >= height { r.logger.Debugf( "the connecting block (height: %d, hash: %s) is too early, skipping the block", height, @@ -80,119 +49,51 @@ func (r *Reporter) handleConnectedBlocks(height int32, header *wire.BlockHeader) return nil } - // if the received header is within the cache's region, then this means the events have - // an overlap with the cache. Then, perform a consistency check. If the block is duplicated, - // then ignore the block, otherwise there is an inconsistency and redo bootstrap - // NOTE: this might happen when bootstrapping is triggered after the reporter - // has subscribed to the BTC blocks - if b := r.btcCache.FindBlock(uint64(height)); b != nil { - if b.BlockHash() == header.BlockHash() { - r.logger.Debugf( - "the connecting block (height: %d, hash: %s) is known to cache, skipping the block", - b.Height, - b.BlockHash().String(), - ) - return nil - } - return fmt.Errorf( - "the connecting block (height: %d, hash: %s) is different from the header (height: %d, hash: %s) at the same height in cache", - height, - header.BlockHash().String(), - b.Height, - b.BlockHash().String(), - ) + if cacheTip.Height+1 < height { + return fmt.Errorf("missing blocks, expected block height: %d, got: %d", cacheTip.Height+1, height) } - // get the block from hash - blockHash := header.BlockHash() - ib, mBlock, err := r.btcClient.GetBlockByHash(&blockHash) - if err != nil { - return fmt.Errorf("failed to get block %v with number %d ,from BTC client: %w", blockHash, height, err) + // Check if the new block connects to the cache cacheTip + parentHash := header.PrevBlock + if parentHash != cacheTip.BlockHash() { + // If the block doesn't connect, clear the cache and bootstrap + r.btcCache.RemoveAll() + return fmt.Errorf("block does not connect to the cache, diff hash, bootstrap required") } - // if the parent of the block is not the tip of the cache, then the cache is not up-to-date, - // and we might have missed some blocks. In this case, restart the bootstrap process. - parentHash := mBlock.Header.PrevBlock - cacheTip := r.btcCache.Tip() // NOTE: cache is guaranteed to be non-empty at this stage - if parentHash != cacheTip.BlockHash() { - return fmt.Errorf("cache (tip %d) is not up-to-date while connecting block %d, restart bootstrap process", cacheTip.Height, ib.Height) + // Block connects to the current chain, add it to the cache + blockHash := header.BlockHash() + ib, _, err := r.btcClient.GetBlockByHash(&blockHash) + if err != nil { + return fmt.Errorf("failed to get block %v with height %d: %w", blockHash, height, err) } - // otherwise, add the block to the cache r.btcCache.Add(ib) - var headersToProcess []*types.IndexedBlock - - if r.reorgList.size() > 0 { - // we are in the middle of reorg, we need to check whether we already have all blocks of better chain - // as reorgs in btc nodes happen only when better chain is available. - // 1. First we get the oldest header from our reorg branch - // 2. Then we get all headers from our cache starting the height of the oldest header of new branch - // 3. then we calculate if work on new branch starting from the first reorged height is larger - // than removed branch work. - oldestBlockFromOldBranch := r.reorgList.getLastRemovedBlock() - currentBranch, err := r.btcCache.GetLastBlocks(oldestBlockFromOldBranch.height) - if err != nil { - panic(fmt.Errorf("failed to get block from cache after reorg: %w", err)) - } - - currentBranchWork := calculateBranchWork(currentBranch) + // Process the new block (submit headers, checkpoints, etc.) + return r.processNewBlock(ib) +} - // if current branch is better than reorg branch, we can submit headers and clear reorg list - if currentBranchWork.GT(r.reorgList.removedBranchWork()) { - r.logger.Debugf("Current branch is better than reorg branch. Length of current branch: %d, work of branch: %s", len(currentBranch), currentBranchWork) - headersToProcess = append(headersToProcess, currentBranch...) - r.reorgList.clear() - } - } else { - headersToProcess = append(headersToProcess, ib) - } +// processNewBlock handles further processing of a newly added block. +func (r *Reporter) processNewBlock(ib *types.IndexedBlock) error { + var headersToProcess []*types.IndexedBlock + headersToProcess = append(headersToProcess, ib) if len(headersToProcess) == 0 { r.logger.Debug("No new headers to submit to Babylon") return nil } - // extracts and submits headers for each blocks in ibs signer := r.babylonClient.MustGetAddr() - _, err = r.ProcessHeaders(signer, headersToProcess) - if err != nil { - r.logger.Warnf("Failed to submit header: %v", err) - } - - // extracts and submits checkpoints for each blocks in ibs - _, _, err = r.ProcessCheckpoints(signer, headersToProcess) - if err != nil { - r.logger.Warnf("Failed to submit checkpoint: %v", err) - } - - return nil -} - -// handleDisconnectedBlocks handles disconnected blocks from the BTC client. -func (r *Reporter) handleDisconnectedBlocks(header *wire.BlockHeader) error { - // get cache tip - cacheTip := r.btcCache.Tip() - if cacheTip == nil { - return fmt.Errorf("cache is empty, restart bootstrap process") - } - // if the block to be disconnected is not the tip of the cache, then the cache is not up-to-date, - if header.BlockHash() != cacheTip.BlockHash() { - return fmt.Errorf("cache is not up-to-date while disconnecting block, restart bootstrap process") + // Process headers + if _, err := r.ProcessHeaders(signer, headersToProcess); err != nil { + r.logger.Warnf("Failed to submit headers: %v", err) } - // at this point, the block to be disconnected is the tip of the cache, so we can - // add it to our reorg list - r.reorgList.addRemovedBlock( - uint64(cacheTip.Height), - cacheTip.Header, - ) - - // otherwise, remove the block from the cache - if err := r.btcCache.RemoveLast(); err != nil { - r.logger.Warnf("Failed to remove last block from cache: %v, restart bootstrap process", err) - panic(err) + // Process checkpoints + if _, _, err := r.ProcessCheckpoints(signer, headersToProcess); err != nil { + r.logger.Warnf("Failed to submit checkpoints: %v", err) } return nil diff --git a/reporter/bootstrapping.go b/reporter/bootstrapping.go index 551d9961..729fb1eb 100644 --- a/reporter/bootstrapping.go +++ b/reporter/bootstrapping.go @@ -60,16 +60,13 @@ func (r *Reporter) checkConsistency() (*consistencyCheckInfo, error) { }, nil } -func (r *Reporter) bootstrap(skipBlockSubscription bool) error { +func (r *Reporter) bootstrap() error { var ( btcLatestBlockHeight uint64 ibs []*types.IndexedBlock err error ) - // if we are bootstraping, we will definitely not handle reorgs - r.reorgList.clear() - // ensure BTC has caught up with BBN header chain if err := r.waitUntilBTCSync(); err != nil { return err @@ -147,12 +144,12 @@ func (r *Reporter) reporterQuitCtx() (context.Context, func()) { return ctx, cancel } -func (r *Reporter) bootstrapWithRetries(skipBlockSubscription bool) { +func (r *Reporter) bootstrapWithRetries() { // if we are exiting, we need to cancel this process ctx, cancel := r.reporterQuitCtx() defer cancel() if err := retry.Do(func() error { - return r.bootstrap(skipBlockSubscription) + return r.bootstrap() }, retry.Context(ctx), bootstrapAttemptsAtt, diff --git a/reporter/reporter.go b/reporter/reporter.go index 90d85529..5c2f757c 100644 --- a/reporter/reporter.go +++ b/reporter/reporter.go @@ -32,7 +32,6 @@ type Reporter struct { // Internal states of the reporter CheckpointCache *types.CheckpointCache btcCache *types.BTCCache - reorgList *reorgList btcConfirmationDepth uint64 checkpointFinalizationTimeout uint64 metrics *metrics.ReporterMetrics @@ -86,7 +85,6 @@ func New( babylonClient: babylonClient, btcNotifier: btcNotifier, CheckpointCache: ckptCache, - reorgList: newReorgList(), btcConfirmationDepth: k, checkpointFinalizationTimeout: w, metrics: metrics, @@ -112,10 +110,21 @@ func (r *Reporter) Start() { } r.quitMu.Unlock() - r.bootstrapWithRetries(false) + r.bootstrapWithRetries() + + if err := r.btcNotifier.Start(); err != nil { + r.logger.Errorf("Failed starting notifier") + return + } + + blockNotifier, err := r.btcNotifier.RegisterBlockEpochNtfn(nil) + if err != nil { + r.logger.Errorf("Failed registering block epoch notifier") + return + } r.wg.Add(1) - go r.blockEventHandler() + go r.blockEventHandler(blockNotifier) // start record time-related metrics r.metrics.RecordMetrics() From f7335e2b05aebec67d9db9c4d9a03b8b84c504be Mon Sep 17 00:00:00 2001 From: lazar Date: Mon, 26 Aug 2024 13:54:30 +0200 Subject: [PATCH 12/24] btc scanner merge handle block logic --- monitor/btcscanner/block_handler.go | 102 ++++++++-------------------- monitor/btcscanner/btc_scanner.go | 13 +++- 2 files changed, 42 insertions(+), 73 deletions(-) diff --git a/monitor/btcscanner/block_handler.go b/monitor/btcscanner/block_handler.go index e17588f8..bfa194de 100644 --- a/monitor/btcscanner/block_handler.go +++ b/monitor/btcscanner/block_handler.go @@ -4,23 +4,12 @@ import ( "errors" "fmt" "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/chainntnfs" ) // blockEventHandler handles connected and disconnected blocks from the BTC client. -func (bs *BtcScanner) blockEventHandler() { +func (bs *BtcScanner) blockEventHandler(blockNotifier *chainntnfs.BlockEpochEvent) { defer bs.wg.Done() - - if err := bs.btcNotifier.Start(); err != nil { - bs.logger.Errorf("Failed starting notifier") - return - } - - blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil) - if err != nil { - bs.logger.Errorf("Failed registering block epoch notifier") - return - } - defer blockNotifier.Cancel() for { @@ -34,67 +23,57 @@ func (bs *BtcScanner) blockEventHandler() { return // channel closed } - tip := bs.UnconfirmedBlockCache.Tip() - - // Determine if a reorg happened to we know which flow to continue - // if the new block has the same height but a different hash. - reorg := false - if tip != nil { - if epoch.Height < tip.Height || - (epoch.Height == tip.Height && epoch.BlockHeader.BlockHash() != tip.Header.BlockHash()) { - reorg = true - } - } - - if !reorg { - if err := bs.handleConnectedBlocks(epoch.BlockHeader); err != nil { - bs.logger.Warnf("failed to handle a connected block at height %d: %s, "+ - "need to restart the bootstrapping process", epoch.Height, err.Error()) - if bs.Synced.Swap(false) { - bs.Bootstrap() - } - } - } else { - if err := bs.handleDisconnectedBlocks(epoch.BlockHeader); err != nil { - bs.logger.Warnf("failed to handle a disconnected block at height %d: %s,"+ - "need to restart the bootstrapping process", epoch.Height, err.Error()) - if bs.Synced.Swap(false) { - bs.Bootstrap() - } + if err := bs.handleNewBlock(epoch.Height, epoch.BlockHeader); err != nil { + bs.logger.Warnf("failed to handle block at height %d: %s, "+ + "need to restart the bootstrapping process", epoch.Height, err.Error()) + if bs.Synced.Swap(false) { + bs.Bootstrap() } } } } } -// handleConnectedBlocks handles connected blocks from the BTC client +// handleNewBlock handles blocks from the BTC client // if new confirmed blocks are found, send them through the channel -func (bs *BtcScanner) handleConnectedBlocks(header *wire.BlockHeader) error { +func (bs *BtcScanner) handleNewBlock(height int32, header *wire.BlockHeader) error { if !bs.Synced.Load() { return errors.New("the btc scanner is not synced") } - // get the block from hash - blockHash := header.BlockHash() - ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash) - if err != nil { - // failing to request the block, which means a bug - panic(err) - } - // get cache tip cacheTip := bs.UnconfirmedBlockCache.Tip() if cacheTip == nil { return errors.New("no unconfirmed blocks found") } - parentHash := ib.Header.PrevBlock + if cacheTip.Height >= height { + bs.logger.Debugf( + "the connecting block (height: %d, hash: %s) is too early, skipping the block", + height, + header.BlockHash().String(), + ) + return nil + } + if cacheTip.Height+1 < height { + return fmt.Errorf("missing blocks, expected block height: %d, got: %d", cacheTip.Height+1, height) + } + + parentHash := header.PrevBlock // if the parent of the block is not the tip of the cache, then the cache is not up-to-date if parentHash != cacheTip.BlockHash() { return errors.New("cache is not up-to-date") } + // get the block from hash + blockHash := header.BlockHash() + ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash) + if err != nil { + // failing to request the block, which means a bug + panic(err) + } + // otherwise, add the block to the cache bs.UnconfirmedBlockCache.Add(ib) @@ -117,24 +96,3 @@ func (bs *BtcScanner) handleConnectedBlocks(header *wire.BlockHeader) error { return nil } - -// handleDisconnectedBlocks handles disconnected blocks from the BTC client. -func (bs *BtcScanner) handleDisconnectedBlocks(header *wire.BlockHeader) error { - // get cache tip - cacheTip := bs.UnconfirmedBlockCache.Tip() - if cacheTip == nil { - return errors.New("cache is empty") - } - - // if the block to be disconnected is not the tip of the cache, then the cache is not up-to-date, - if header.BlockHash() != cacheTip.BlockHash() { - return errors.New("cache is out-of-sync") - } - - // otherwise, remove the block from the cache - if err := bs.UnconfirmedBlockCache.RemoveLast(); err != nil { - return fmt.Errorf("failed to remove last block from cache: %v", err) - } - - return nil -} diff --git a/monitor/btcscanner/btc_scanner.go b/monitor/btcscanner/btc_scanner.go index a102dbb7..ecb0946f 100644 --- a/monitor/btcscanner/btc_scanner.go +++ b/monitor/btcscanner/btc_scanner.go @@ -94,9 +94,20 @@ func (bs *BtcScanner) Start() { bs.Started.Store(true) bs.logger.Info("the BTC scanner is started") + if err := bs.btcNotifier.Start(); err != nil { + bs.logger.Errorf("Failed starting notifier") + return + } + + blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil) + if err != nil { + bs.logger.Errorf("Failed registering block epoch notifier") + return + } + // start handling new blocks bs.wg.Add(1) - go bs.blockEventHandler() + go bs.blockEventHandler(blockNotifier) for bs.Started.Load() { select { From df198b34da73d7dc5a40212bdc5e7b1b44860390 Mon Sep 17 00:00:00 2001 From: lazar Date: Mon, 26 Aug 2024 15:18:01 +0200 Subject: [PATCH 13/24] rm unused code --- reporter/reorg_list.go | 76 ------------------------------------------ reporter/utils.go | 10 ------ 2 files changed, 86 deletions(-) delete mode 100644 reporter/reorg_list.go diff --git a/reporter/reorg_list.go b/reporter/reorg_list.go deleted file mode 100644 index 6e24c938..00000000 --- a/reporter/reorg_list.go +++ /dev/null @@ -1,76 +0,0 @@ -package reporter - -import ( - "sync" - - sdkmath "cosmossdk.io/math" - btclightclienttypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" - "github.com/btcsuite/btcd/wire" -) - -type removedBlock struct { - height uint64 - header *wire.BlockHeader -} - -// Help data structure to keep track of removed blocks. -// NOTE: This is not generic data structure, and must be used with conjunction with -// reporter and btc cache -type reorgList struct { - sync.Mutex - workOfRemovedBlocks sdkmath.Uint - removedBlocks []*removedBlock -} - -func newReorgList() *reorgList { - return &reorgList{ - removedBlocks: []*removedBlock{}, - workOfRemovedBlocks: sdkmath.ZeroUint(), - } -} - -// addRemovedBlock add currently removed block to the end of the list. Re-orgs -// are started from the tip of the chain and go backwards, this means -// that oldest removed block is at the end of the list. -func (r *reorgList) addRemovedBlock( - height uint64, - header *wire.BlockHeader) { - headerWork := btclightclienttypes.CalcHeaderWork(header) - r.Lock() - defer r.Unlock() - - newWork := btclightclienttypes.CumulativeWork(headerWork, r.workOfRemovedBlocks) - r.removedBlocks = append(r.removedBlocks, &removedBlock{height, header}) - r.workOfRemovedBlocks = newWork -} - -func (r *reorgList) getLastRemovedBlock() *removedBlock { - r.Lock() - defer r.Unlock() - if len(r.removedBlocks) == 0 { - return nil - } - - return r.removedBlocks[len(r.removedBlocks)-1] -} - -func (r *reorgList) clear() { - r.Lock() - defer r.Unlock() - - r.removedBlocks = []*removedBlock{} - r.workOfRemovedBlocks = sdkmath.ZeroUint() -} - -func (r *reorgList) size() int { - r.Lock() - defer r.Unlock() - - return len(r.removedBlocks) -} - -func (r *reorgList) removedBranchWork() sdkmath.Uint { - r.Lock() - defer r.Unlock() - return r.workOfRemovedBlocks -} diff --git a/reporter/utils.go b/reporter/utils.go index 59dae461..ce1ef528 100644 --- a/reporter/utils.go +++ b/reporter/utils.go @@ -7,7 +7,6 @@ import ( pv "github.com/cosmos/relayer/v2/relayer/provider" - sdkmath "cosmossdk.io/math" "github.com/babylonlabs-io/babylon/types/retry" btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types" btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" @@ -224,15 +223,6 @@ func (r *Reporter) ProcessCheckpoints(signer string, ibs []*types.IndexedBlock) return numCkptSegs, numMatchedCkpts, err } -func calculateBranchWork(branch []*types.IndexedBlock) sdkmath.Uint { - var currenWork = sdkmath.ZeroUint() - for _, h := range branch { - headerWork := btclctypes.CalcHeaderWork(h.Header) - currenWork = btclctypes.CumulativeWork(headerWork, currenWork) - } - return currenWork -} - // push msg to channel c, or quit if quit channel is closed func PushOrQuit[T any](c chan<- T, msg T, quit <-chan struct{}) { select { From e47d580d068e8d8598fd127f326ca99fb6aaa29e Mon Sep 17 00:00:00 2001 From: lazar Date: Tue, 27 Aug 2024 10:39:44 +0200 Subject: [PATCH 14/24] combine booststrap and blockevent handle --- monitor/btcscanner/block_handler.go | 33 ++++++++++++++++++-------- monitor/btcscanner/btc_scanner.go | 20 +--------------- monitor/btcscanner/btc_scanner_test.go | 8 ++----- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/monitor/btcscanner/block_handler.go b/monitor/btcscanner/block_handler.go index bfa194de..704d1b9b 100644 --- a/monitor/btcscanner/block_handler.go +++ b/monitor/btcscanner/block_handler.go @@ -7,9 +7,28 @@ import ( "github.com/lightningnetwork/lnd/chainntnfs" ) -// blockEventHandler handles connected and disconnected blocks from the BTC client. -func (bs *BtcScanner) blockEventHandler(blockNotifier *chainntnfs.BlockEpochEvent) { +// bootstrapAndBlockEventHandler handles connected and disconnected blocks from the BTC client. +func (bs *BtcScanner) bootstrapAndBlockEventHandler() { defer bs.wg.Done() + + bs.Bootstrap() + + var blockEpoch *chainntnfs.BlockEpoch + bestKnownBlock := bs.UnconfirmedBlockCache.Tip() + if bestKnownBlock != nil { + hash := bestKnownBlock.BlockHash() + blockEpoch = &chainntnfs.BlockEpoch{ + Hash: &hash, + Height: bestKnownBlock.Height, + BlockHeader: bestKnownBlock.Header, + } + } + // register the notifier with the best known tip + blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(blockEpoch) + if err != nil { + bs.logger.Errorf("Failed registering block epoch notifier") + return + } defer blockNotifier.Cancel() for { @@ -26,9 +45,7 @@ func (bs *BtcScanner) blockEventHandler(blockNotifier *chainntnfs.BlockEpochEven if err := bs.handleNewBlock(epoch.Height, epoch.BlockHeader); err != nil { bs.logger.Warnf("failed to handle block at height %d: %s, "+ "need to restart the bootstrapping process", epoch.Height, err.Error()) - if bs.Synced.Swap(false) { - bs.Bootstrap() - } + bs.Bootstrap() } } } @@ -37,10 +54,6 @@ func (bs *BtcScanner) blockEventHandler(blockNotifier *chainntnfs.BlockEpochEven // handleNewBlock handles blocks from the BTC client // if new confirmed blocks are found, send them through the channel func (bs *BtcScanner) handleNewBlock(height int32, header *wire.BlockHeader) error { - if !bs.Synced.Load() { - return errors.New("the btc scanner is not synced") - } - // get cache tip cacheTip := bs.UnconfirmedBlockCache.Tip() if cacheTip == nil { @@ -71,7 +84,7 @@ func (bs *BtcScanner) handleNewBlock(height int32, header *wire.BlockHeader) err ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash) if err != nil { // failing to request the block, which means a bug - panic(err) + panic(fmt.Errorf("failed to request block by hash: %s", blockHash.String())) } // otherwise, add the block to the cache diff --git a/monitor/btcscanner/btc_scanner.go b/monitor/btcscanner/btc_scanner.go index ecb0946f..16da44ab 100644 --- a/monitor/btcscanner/btc_scanner.go +++ b/monitor/btcscanner/btc_scanner.go @@ -40,8 +40,6 @@ type BtcScanner struct { blockHeaderChan chan *wire.BlockHeader checkpointsChan chan *types.CheckpointRecord - Synced *atomic.Bool - wg sync.WaitGroup Started *atomic.Bool quit chan struct{} @@ -75,7 +73,6 @@ func New( ConfirmedBlocksChan: confirmedBlocksChan, blockHeaderChan: headersChan, checkpointsChan: ckptsChan, - Synced: atomic.NewBool(false), Started: atomic.NewBool(false), quit: make(chan struct{}), }, nil @@ -88,9 +85,6 @@ func (bs *BtcScanner) Start() { return } - // the bootstrapping should not block the main thread - go bs.Bootstrap() - bs.Started.Store(true) bs.logger.Info("the BTC scanner is started") @@ -99,15 +93,9 @@ func (bs *BtcScanner) Start() { return } - blockNotifier, err := bs.btcNotifier.RegisterBlockEpochNtfn(nil) - if err != nil { - bs.logger.Errorf("Failed registering block epoch notifier") - return - } - // start handling new blocks bs.wg.Add(1) - go bs.blockEventHandler(blockNotifier) + go bs.bootstrapAndBlockEventHandler() for bs.Started.Load() { select { @@ -141,12 +129,6 @@ func (bs *BtcScanner) Bootstrap() { err error ) - if bs.Synced.Load() { - // the scanner is already synced - return - } - defer bs.Synced.Store(true) - if bs.confirmedTipBlock != nil { firstUnconfirmedHeight = uint64(bs.confirmedTipBlock.Height + 1) } else { diff --git a/monitor/btcscanner/btc_scanner_test.go b/monitor/btcscanner/btc_scanner_test.go index 5a5c302b..cb91d70d 100644 --- a/monitor/btcscanner/btc_scanner_test.go +++ b/monitor/btcscanner/btc_scanner_test.go @@ -5,15 +5,13 @@ import ( "testing" "github.com/babylonlabs-io/babylon/testutil/datagen" - "github.com/golang/mock/gomock" - "github.com/stretchr/testify/require" - "go.uber.org/atomic" - "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/monitor/btcscanner" vdatagen "github.com/babylonlabs-io/vigilante/testutil/datagen" "github.com/babylonlabs-io/vigilante/testutil/mocks" "github.com/babylonlabs-io/vigilante/types" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" ) func FuzzBootStrap(f *testing.F) { @@ -44,7 +42,6 @@ func FuzzBootStrap(f *testing.F) { K: k, ConfirmedBlocksChan: make(chan *types.IndexedBlock), UnconfirmedBlockCache: cache, - Synced: atomic.NewBool(false), } logger, err := config.NewRootLogger("auto", "debug") require.NoError(t, err) @@ -57,6 +54,5 @@ func FuzzBootStrap(f *testing.F) { } }() btcScanner.Bootstrap() - require.True(t, btcScanner.Synced.Load()) }) } From 4fd51763f729c5590b98b163efb3398bfa87d804 Mon Sep 17 00:00:00 2001 From: lazar Date: Tue, 27 Aug 2024 15:06:00 +0200 Subject: [PATCH 15/24] remove btcd handling in code --- btcclient/client_wallet.go | 36 ++--- btcclient/notifier.go | 187 +++++++----------------- cmd/vigilante/cmd/btcstaking_tracker.go | 2 +- cmd/vigilante/cmd/monitor.go | 2 +- cmd/vigilante/cmd/reporter.go | 2 +- config/bitcoin.go | 70 ++++----- e2etest/atomicslasher_e2e_test.go | 4 +- e2etest/reporter_e2e_test.go | 6 +- e2etest/slasher_e2e_test.go | 8 +- e2etest/test_manager.go | 2 - e2etest/unbondingwatcher_e2e_test.go | 2 +- submitter/relayer/estimator.go | 68 +++------ submitter/submitter.go | 2 +- types/utils.go | 19 +-- 14 files changed, 125 insertions(+), 285 deletions(-) diff --git a/btcclient/client_wallet.go b/btcclient/client_wallet.go index 16c964bd..79ec7f28 100644 --- a/btcclient/client_wallet.go +++ b/btcclient/client_wallet.go @@ -13,7 +13,6 @@ import ( "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/netparams" - "github.com/babylonlabs-io/vigilante/types" ) // NewWallet creates a new BTC wallet @@ -30,38 +29,21 @@ func NewWallet(cfg *config.BTCConfig, parentLogger *zap.Logger) (*Client, error) wallet.params = params wallet.logger = parentLogger.With(zap.String("module", "btcclient_wallet")).Sugar() - connCfg := &rpcclient.ConnConfig{} - switch cfg.BtcBackend { - case types.Bitcoind: - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg = &rpcclient.ConnConfig{ - // this will work with node loaded with multiple wallets - Host: cfg.Endpoint + "/wallet/" + cfg.WalletName, - HTTPPostMode: true, - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - } - case types.Btcd: - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg = &rpcclient.ConnConfig{ - Host: cfg.WalletEndpoint, - Endpoint: "ws", // websocket - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - Certificates: cfg.ReadWalletCAFile(), - } + connCfg := &rpcclient.ConnConfig{ + // this will work with node loaded with multiple wallets + Host: cfg.Endpoint + "/wallet/" + cfg.WalletName, + HTTPPostMode: true, + User: cfg.Username, + Pass: cfg.Password, + DisableTLS: cfg.DisableClientTLS, } rpcClient, err := rpcclient.New(connCfg, nil) if err != nil { - return nil, fmt.Errorf("failed to create rpc client to BTC for %s backend: %w", cfg.BtcBackend, err) + return nil, fmt.Errorf("failed to create rpc client to BTC: %w", err) } - wallet.logger.Infof("Successfully connected to %s backend", cfg.BtcBackend) + wallet.logger.Infof("Successfully connected to bitcoind") wallet.Client = rpcClient diff --git a/btcclient/notifier.go b/btcclient/notifier.go index 2ecc10ca..09939859 100644 --- a/btcclient/notifier.go +++ b/btcclient/notifier.go @@ -1,22 +1,17 @@ package btcclient import ( - "encoding/hex" "fmt" "github.com/babylonlabs-io/vigilante/netparams" "net" - "os" "time" "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/types" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcwallet/chain" "github.com/lightningnetwork/lnd/blockcache" "github.com/lightningnetwork/lnd/chainntnfs" "github.com/lightningnetwork/lnd/chainntnfs/bitcoindnotify" - "github.com/lightningnetwork/lnd/chainntnfs/btcdnotify" ) type Btcd struct { @@ -60,45 +55,17 @@ func DefaultBitcoindConfig() Bitcoind { } } -type BtcNodeBackendConfig struct { - Btcd *Btcd - Bitcoind *Bitcoind - ActiveNodeBackend types.SupportedBtcBackend -} - -func CfgToBtcNodeBackendConfig(cfg config.BTCConfig, rawCert string) *BtcNodeBackendConfig { - switch cfg.BtcBackend { - case types.Bitcoind: - defaultBitcoindCfg := DefaultBitcoindConfig() - // Re-rewrite defaults by values from global cfg - defaultBitcoindCfg.RPCHost = cfg.Endpoint - defaultBitcoindCfg.RPCUser = cfg.Username - defaultBitcoindCfg.RPCPass = cfg.Password - defaultBitcoindCfg.ZMQPubRawBlock = cfg.ZmqBlockEndpoint - defaultBitcoindCfg.ZMQPubRawTx = cfg.ZmqTxEndpoint - defaultBitcoindCfg.EstimateMode = cfg.EstimateMode +func ToBitcoindConfig(cfg config.BTCConfig) *Bitcoind { + defaultBitcoindCfg := DefaultBitcoindConfig() + // Re-rewrite defaults by values from global cfg + defaultBitcoindCfg.RPCHost = cfg.Endpoint + defaultBitcoindCfg.RPCUser = cfg.Username + defaultBitcoindCfg.RPCPass = cfg.Password + defaultBitcoindCfg.ZMQPubRawBlock = cfg.ZmqBlockEndpoint + defaultBitcoindCfg.ZMQPubRawTx = cfg.ZmqTxEndpoint + defaultBitcoindCfg.EstimateMode = cfg.EstimateMode - return &BtcNodeBackendConfig{ - ActiveNodeBackend: types.Bitcoind, - Bitcoind: &defaultBitcoindCfg, - } - case types.Btcd: - return &BtcNodeBackendConfig{ - ActiveNodeBackend: types.Btcd, - Btcd: &Btcd{ - RPCHost: cfg.Endpoint, - RPCUser: cfg.Username, - RPCPass: cfg.Password, - RPCCert: cfg.CAFile, - RawCert: rawCert, - DisableTLS: cfg.DisableClientTLS, - // TODO: Make block cache size configurable. Note: this is value is in bytes. - BlockCacheSize: config.DefaultBtcblockCacheSize, - }, - } - default: - panic(fmt.Sprintf("unknown btc backend: %v", cfg.BtcBackend)) - } + return &defaultBitcoindCfg } type NodeBackend struct { @@ -110,9 +77,6 @@ type HintCache interface { chainntnfs.ConfirmHintCache } -// type for disabled hint cache -// TODO: Determine if we need hint cache backed up by database which is provided -// by lnd. type EmptyHintCache struct{} var _ HintCache = (*EmptyHintCache)(nil) @@ -148,116 +112,63 @@ func BuildDialer(rpcHost string) func(string) (net.Conn, error) { } func NewNodeBackend( - cfg *BtcNodeBackendConfig, + cfg *Bitcoind, params *chaincfg.Params, hintCache HintCache, ) (*NodeBackend, error) { - switch cfg.ActiveNodeBackend { - case types.Bitcoind: - bitcoindCfg := &chain.BitcoindConfig{ - ChainParams: params, - Host: cfg.Bitcoind.RPCHost, - User: cfg.Bitcoind.RPCUser, - Pass: cfg.Bitcoind.RPCPass, - Dialer: BuildDialer(cfg.Bitcoind.RPCHost), - PrunedModeMaxPeers: cfg.Bitcoind.PrunedNodeMaxPeers, - } - - if cfg.Bitcoind.RPCPolling { - bitcoindCfg.PollingConfig = &chain.PollingConfig{ - BlockPollingInterval: cfg.Bitcoind.BlockPollingInterval, - TxPollingInterval: cfg.Bitcoind.TxPollingInterval, - TxPollingIntervalJitter: config.DefaultTxPollingJitter, - } - } else { - bitcoindCfg.ZMQConfig = &chain.ZMQConfig{ - ZMQBlockHost: cfg.Bitcoind.ZMQPubRawBlock, - ZMQTxHost: cfg.Bitcoind.ZMQPubRawTx, - ZMQReadDeadline: cfg.Bitcoind.ZMQReadDeadline, - MempoolPollingInterval: cfg.Bitcoind.TxPollingInterval, - PollingIntervalJitter: config.DefaultTxPollingJitter, - } - } - - bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg) - if err != nil { - return nil, err - } - - if err := bitcoindConn.Start(); err != nil { - return nil, fmt.Errorf("unable to connect to "+ - "bitcoind: %v", err) - } - - chainNotifier := bitcoindnotify.New( - bitcoindConn, params, hintCache, - hintCache, blockcache.NewBlockCache(cfg.Bitcoind.BlockCacheSize), - ) - - return &NodeBackend{ - ChainNotifier: chainNotifier, - }, nil - - case types.Btcd: - btcdUser := cfg.Btcd.RPCUser - btcdPass := cfg.Btcd.RPCPass - btcdHost := cfg.Btcd.RPCHost - - var certs []byte - if !cfg.Btcd.DisableTLS { - if cfg.Btcd.RawCert != "" { - decoded, err := hex.DecodeString(cfg.Btcd.RawCert) - if err != nil { - return nil, fmt.Errorf("error decoding btcd cert: %v", err) - } - certs = decoded + bitcoindCfg := &chain.BitcoindConfig{ + ChainParams: params, + Host: cfg.RPCHost, + User: cfg.RPCUser, + Pass: cfg.RPCPass, + Dialer: BuildDialer(cfg.RPCHost), + PrunedModeMaxPeers: cfg.PrunedNodeMaxPeers, + } - } else { - certificates, err := os.ReadFile(cfg.Btcd.RPCCert) - if err != nil { - return nil, fmt.Errorf("error reading btcd cert file: %v", err) - } - certs = certificates - } + if cfg.RPCPolling { + bitcoindCfg.PollingConfig = &chain.PollingConfig{ + BlockPollingInterval: cfg.BlockPollingInterval, + TxPollingInterval: cfg.TxPollingInterval, + TxPollingIntervalJitter: config.DefaultTxPollingJitter, } - - rpcConfig := &rpcclient.ConnConfig{ - Host: btcdHost, - Endpoint: "ws", - User: btcdUser, - Pass: btcdPass, - Certificates: certs, - DisableTLS: cfg.Btcd.DisableTLS, - DisableConnectOnNew: true, - DisableAutoReconnect: false, + } else { + bitcoindCfg.ZMQConfig = &chain.ZMQConfig{ + ZMQBlockHost: cfg.ZMQPubRawBlock, + ZMQTxHost: cfg.ZMQPubRawTx, + ZMQReadDeadline: cfg.ZMQReadDeadline, + MempoolPollingInterval: cfg.TxPollingInterval, + PollingIntervalJitter: config.DefaultTxPollingJitter, } + } - chainNotifier, err := btcdnotify.New( - rpcConfig, params, hintCache, - hintCache, blockcache.NewBlockCache(cfg.Btcd.BlockCacheSize), - ) + bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg) + if err != nil { + return nil, err + } - if err != nil { - return nil, err - } + if err := bitcoindConn.Start(); err != nil { + return nil, fmt.Errorf("unable to connect to "+ + "bitcoind: %v", err) + } - return &NodeBackend{ - ChainNotifier: chainNotifier, - }, nil + chainNotifier := bitcoindnotify.New( + bitcoindConn, params, hintCache, + hintCache, blockcache.NewBlockCache(cfg.BlockCacheSize), + ) - default: - return nil, fmt.Errorf("unknown node backend: %v", cfg.ActiveNodeBackend) - } + return &NodeBackend{ + ChainNotifier: chainNotifier, + }, nil } // NewNodeBackendWithParams creates a new NodeBackend by incorporating parameter retrieval and config conversion. -func NewNodeBackendWithParams(cfg config.BTCConfig, rawCert string) (*NodeBackend, error) { +func NewNodeBackendWithParams(cfg config.BTCConfig) (*NodeBackend, error) { btcParams, err := netparams.GetBTCParams(cfg.NetParams) if err != nil { return nil, fmt.Errorf("failed to get BTC net params: %w", err) } - btcNotifier, err := NewNodeBackend(CfgToBtcNodeBackendConfig(cfg, rawCert), btcParams, &EmptyHintCache{}) + btcNotifier, err := NewNodeBackend(ToBitcoindConfig(cfg), btcParams, &EmptyHintCache{}) if err != nil { return nil, fmt.Errorf("failed to initialize notifier: %w", err) } diff --git a/cmd/vigilante/cmd/btcstaking_tracker.go b/cmd/vigilante/cmd/btcstaking_tracker.go index d4794170..1874b753 100644 --- a/cmd/vigilante/cmd/btcstaking_tracker.go +++ b/cmd/vigilante/cmd/btcstaking_tracker.go @@ -68,7 +68,7 @@ func GetBTCStakingTracker() *cobra.Command { // create BTC notifier // TODO: is it possible to merge BTC client and BTC notifier? - btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC) if err != nil { panic(err) } diff --git a/cmd/vigilante/cmd/monitor.go b/cmd/vigilante/cmd/monitor.go index 49259c50..fe2b7423 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -74,7 +74,7 @@ func GetMonitorCmd() *cobra.Command { monitorMetrics := metrics.NewMonitorMetrics() // create the chain notifier - btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC) if err != nil { panic(err) } diff --git a/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index 5c9a0b60..0d763d18 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -62,7 +62,7 @@ func GetReporterCmd() *cobra.Command { reporterMetrics := metrics.NewReporterMetrics() // create the chain notifier - btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC, "") + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC) if err != nil { panic(err) } diff --git a/config/bitcoin.go b/config/bitcoin.go index aa2b5e97..1fb3be31 100644 --- a/config/bitcoin.go +++ b/config/bitcoin.go @@ -12,27 +12,26 @@ import ( // BTCConfig defines configuration for the Bitcoin client type BTCConfig struct { - DisableClientTLS bool `mapstructure:"no-client-tls"` - CAFile string `mapstructure:"ca-file"` - Endpoint string `mapstructure:"endpoint"` - WalletEndpoint string `mapstructure:"wallet-endpoint"` - WalletPassword string `mapstructure:"wallet-password"` - WalletName string `mapstructure:"wallet-name"` - WalletCAFile string `mapstructure:"wallet-ca-file"` - WalletLockTime int64 `mapstructure:"wallet-lock-time"` // time duration in which the wallet remains unlocked, in seconds - TxFeeMin chainfee.SatPerKVByte `mapstructure:"tx-fee-min"` // minimum tx fee, sat/kvb - TxFeeMax chainfee.SatPerKVByte `mapstructure:"tx-fee-max"` // maximum tx fee, sat/kvb - DefaultFee chainfee.SatPerKVByte `mapstructure:"default-fee"` // default BTC tx fee in case estimation fails, sat/kvb - EstimateMode string `mapstructure:"estimate-mode"` // the BTC tx fee estimate mode, which is only used by bitcoind, must be either ECONOMICAL or CONSERVATIVE - TargetBlockNum int64 `mapstructure:"target-block-num"` // this implies how soon the tx is estimated to be included in a block, e.g., 1 means the tx is estimated to be included in the next block - NetParams string `mapstructure:"net-params"` - Username string `mapstructure:"username"` - Password string `mapstructure:"password"` - ReconnectAttempts int `mapstructure:"reconnect-attempts"` - BtcBackend types.SupportedBtcBackend `mapstructure:"btc-backend"` - ZmqSeqEndpoint string `mapstructure:"zmq-seq-endpoint"` - ZmqBlockEndpoint string `mapstructure:"zmq-block-endpoint"` - ZmqTxEndpoint string `mapstructure:"zmq-tx-endpoint"` + DisableClientTLS bool `mapstructure:"no-client-tls"` + CAFile string `mapstructure:"ca-file"` + Endpoint string `mapstructure:"endpoint"` + WalletEndpoint string `mapstructure:"wallet-endpoint"` + WalletPassword string `mapstructure:"wallet-password"` + WalletName string `mapstructure:"wallet-name"` + WalletCAFile string `mapstructure:"wallet-ca-file"` + WalletLockTime int64 `mapstructure:"wallet-lock-time"` // time duration in which the wallet remains unlocked, in seconds + TxFeeMin chainfee.SatPerKVByte `mapstructure:"tx-fee-min"` // minimum tx fee, sat/kvb + TxFeeMax chainfee.SatPerKVByte `mapstructure:"tx-fee-max"` // maximum tx fee, sat/kvb + DefaultFee chainfee.SatPerKVByte `mapstructure:"default-fee"` // default BTC tx fee in case estimation fails, sat/kvb + EstimateMode string `mapstructure:"estimate-mode"` // the BTC tx fee estimate mode, which is only used by bitcoind, must be either ECONOMICAL or CONSERVATIVE + TargetBlockNum int64 `mapstructure:"target-block-num"` // this implies how soon the tx is estimated to be included in a block, e.g., 1 means the tx is estimated to be included in the next block + NetParams string `mapstructure:"net-params"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + ReconnectAttempts int `mapstructure:"reconnect-attempts"` + ZmqSeqEndpoint string `mapstructure:"zmq-seq-endpoint"` + ZmqBlockEndpoint string `mapstructure:"zmq-block-endpoint"` + ZmqTxEndpoint string `mapstructure:"zmq-tx-endpoint"` } func (cfg *BTCConfig) Validate() error { @@ -44,27 +43,21 @@ func (cfg *BTCConfig) Validate() error { return errors.New("invalid net params") } - if _, ok := types.GetValidBtcBackends()[cfg.BtcBackend]; !ok { - return errors.New("invalid btc backend") + // TODO: implement regex validation for zmq endpoint + if cfg.ZmqBlockEndpoint == "" { + return errors.New("zmq block endpoint cannot be empty") } - if cfg.BtcBackend == types.Bitcoind { - // TODO: implement regex validation for zmq endpoint - if cfg.ZmqBlockEndpoint == "" { - return errors.New("zmq block endpoint cannot be empty") - } - - if cfg.ZmqTxEndpoint == "" { - return errors.New("zmq tx endpoint cannot be empty") - } + if cfg.ZmqTxEndpoint == "" { + return errors.New("zmq tx endpoint cannot be empty") + } - if cfg.ZmqSeqEndpoint == "" { - return errors.New("zmq seq endpoint cannot be empty") - } + if cfg.ZmqSeqEndpoint == "" { + return errors.New("zmq seq endpoint cannot be empty") + } - if cfg.EstimateMode != "ECONOMICAL" && cfg.EstimateMode != "CONSERVATIVE" { - return errors.New("estimate-mode must be either ECONOMICAL or CONSERVATIVE when the backend is bitcoind") - } + if cfg.EstimateMode != "ECONOMICAL" && cfg.EstimateMode != "CONSERVATIVE" { + return errors.New("estimate-mode must be either ECONOMICAL or CONSERVATIVE when the backend is bitcoind") } if cfg.TargetBlockNum <= 0 { @@ -117,7 +110,6 @@ func DefaultBTCConfig() BTCConfig { WalletName: "default", WalletCAFile: defaultBtcWalletCAFile, WalletLockTime: 10, - BtcBackend: types.Btcd, TxFeeMax: chainfee.SatPerKVByte(20 * 1000), // 20,000sat/kvb = 20sat/vbyte TxFeeMin: chainfee.SatPerKVByte(1 * 1000), // 1,000sat/kvb = 1sat/vbyte DefaultFee: chainfee.SatPerKVByte(1 * 1000), // 1,000sat/kvb = 1sat/vbyte diff --git a/e2etest/atomicslasher_e2e_test.go b/e2etest/atomicslasher_e2e_test.go index c9799026..7672a4a6 100644 --- a/e2etest/atomicslasher_e2e_test.go +++ b/e2etest/atomicslasher_e2e_test.go @@ -39,7 +39,7 @@ func TestAtomicSlasher(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) @@ -159,7 +159,7 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) diff --git a/e2etest/reporter_e2e_test.go b/e2etest/reporter_e2e_test.go index 9fec7d97..2d25fa35 100644 --- a/e2etest/reporter_e2e_test.go +++ b/e2etest/reporter_e2e_test.go @@ -65,7 +65,7 @@ func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) { // create the chain notifier btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) require.NoError(t, err) - btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcCfg := btcclient.ToBitcoindConfig(tm.Config.BTC) btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) require.NoError(t, err) @@ -128,7 +128,7 @@ func TestRelayHeadersAndHandleRollbacks(t *testing.T) { btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) require.NoError(t, err) - btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcCfg := btcclient.ToBitcoindConfig(tm.Config.BTC) btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) require.NoError(t, err) @@ -179,7 +179,7 @@ func TestHandleReorgAfterRestart(t *testing.T) { btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) require.NoError(t, err) - btcCfg := btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, "") + btcCfg := btcclient.ToBitcoindConfig(tm.Config.BTC) btcNotifier, err := btcclient.NewNodeBackend(btcCfg, btcParams, &btcclient.EmptyHintCache{}) require.NoError(t, err) diff --git a/e2etest/slasher_e2e_test.go b/e2etest/slasher_e2e_test.go index b9a94e3f..e074e349 100644 --- a/e2etest/slasher_e2e_test.go +++ b/e2etest/slasher_e2e_test.go @@ -31,7 +31,7 @@ func TestSlasher_GracefulShutdown(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) @@ -83,7 +83,7 @@ func TestSlasher_Slasher(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) @@ -153,7 +153,7 @@ func TestSlasher_SlashingUnbonding(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) @@ -248,7 +248,7 @@ func TestSlasher_Bootstrapping(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) diff --git a/e2etest/test_manager.go b/e2etest/test_manager.go index e7e2b003..7677b031 100644 --- a/e2etest/test_manager.go +++ b/e2etest/test_manager.go @@ -17,7 +17,6 @@ import ( btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types" "github.com/babylonlabs-io/vigilante/btcclient" "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/types" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -43,7 +42,6 @@ func defaultVigilanteConfig() *config.Config { defaultConfig.BTC.NetParams = regtestParams.Name defaultConfig.BTC.Endpoint = "127.0.0.1:18443" // Config setting necessary to connect btcwallet daemon - defaultConfig.BTC.BtcBackend = types.Bitcoind defaultConfig.BTC.WalletEndpoint = "127.0.0.1:18554" defaultConfig.BTC.WalletPassword = "pass" defaultConfig.BTC.Username = "user" diff --git a/e2etest/unbondingwatcher_e2e_test.go b/e2etest/unbondingwatcher_e2e_test.go index fc60c51f..10fa7086 100644 --- a/e2etest/unbondingwatcher_e2e_test.go +++ b/e2etest/unbondingwatcher_e2e_test.go @@ -33,7 +33,7 @@ func TestUnbondingWatcher(t *testing.T) { // TODO: our config only support btcd wallet tls, not btcd directly tm.Config.BTC.DisableClientTLS = false backend, err := btcclient.NewNodeBackend( - btcclient.CfgToBtcNodeBackendConfig(tm.Config.BTC, ""), + btcclient.ToBitcoindConfig(tm.Config.BTC), &chaincfg.RegressionNetParams, &emptyHintCache, ) diff --git a/submitter/relayer/estimator.go b/submitter/relayer/estimator.go index 33b2ec32..d460b48b 100644 --- a/submitter/relayer/estimator.go +++ b/submitter/relayer/estimator.go @@ -7,59 +7,33 @@ import ( "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/types" ) -// NewFeeEstimator creates a fee estimator based on the given backend -// currently, we only support bitcoind and btcd +// NewFeeEstimator creates a fee estimator for bitcoind func NewFeeEstimator(cfg *config.BTCConfig) (chainfee.Estimator, error) { + // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd + // when handling signet. + // todo(lazar955): check if we should start specifying this, considering we are no longer using btcd based on comment above ^^ + connCfg := &rpcclient.ConnConfig{ + // this will work with node loaded with multiple wallets + Host: cfg.Endpoint + "/wallet/" + cfg.WalletName, + HTTPPostMode: true, + User: cfg.Username, + Pass: cfg.Password, + DisableTLS: cfg.DisableClientTLS, + } + + estimator, err := chainfee.NewBitcoindEstimator( + *connCfg, cfg.EstimateMode, cfg.DefaultFee.FeePerKWeight(), + ) - var connCfg *rpcclient.ConnConfig - var est chainfee.Estimator - switch cfg.BtcBackend { - case types.Bitcoind: - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg = &rpcclient.ConnConfig{ - // this will work with node loaded with multiple wallets - Host: cfg.Endpoint + "/wallet/" + cfg.WalletName, - HTTPPostMode: true, - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - } - bitcoindEst, err := chainfee.NewBitcoindEstimator( - *connCfg, cfg.EstimateMode, cfg.DefaultFee.FeePerKWeight(), - ) - if err != nil { - return nil, fmt.Errorf("failed to create fee estimator for %s backend: %w", types.Bitcoind, err) - } - est = bitcoindEst - case types.Btcd: - // TODO Currently we are not using Params field of rpcclient.ConnConfig due to bug in btcd - // when handling signet. - connCfg = &rpcclient.ConnConfig{ - Host: cfg.WalletEndpoint, - Endpoint: "ws", // websocket - User: cfg.Username, - Pass: cfg.Password, - DisableTLS: cfg.DisableClientTLS, - Certificates: cfg.ReadWalletCAFile(), - } - btcdEst, err := chainfee.NewBtcdEstimator( - *connCfg, cfg.DefaultFee.FeePerKWeight(), - ) - if err != nil { - return nil, fmt.Errorf("failed to create fee estimator for %s backend: %w", types.Btcd, err) - } - est = btcdEst - default: - return nil, fmt.Errorf("unsupported backend for fee estimator") + if err != nil { + return nil, fmt.Errorf("failed to create fee estimator: %w", err) } - if err := est.Start(); err != nil { - return nil, fmt.Errorf("failed to initiate the fee estimator for %s backend: %w", cfg.BtcBackend, err) + if err := estimator.Start(); err != nil { + return nil, fmt.Errorf("failed to initiate the fee estimator: %w", err) } - return est, nil + return estimator, nil } diff --git a/submitter/submitter.go b/submitter/submitter.go index f689cb49..da1fd744 100644 --- a/submitter/submitter.go +++ b/submitter/submitter.go @@ -69,7 +69,7 @@ func New( if err != nil { return nil, fmt.Errorf("failed to create fee estimator: %w", err) } - logger.Sugar().Infof("Successfully started fee estimator for %s backend", btcCfg.BtcBackend) + logger.Sugar().Infof("Successfully started fee estimator for bitcoind") r := relayer.New( btcWallet, diff --git a/types/utils.go b/types/utils.go index 4529782e..bb39fd52 100644 --- a/types/utils.go +++ b/types/utils.go @@ -1,13 +1,12 @@ package types import ( - "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/wire" ) type ( SupportedBtcNetwork string - SupportedBtcBackend string ) const ( @@ -16,19 +15,12 @@ const ( BtcSimnet SupportedBtcNetwork = "simnet" BtcRegtest SupportedBtcNetwork = "regtest" BtcSignet SupportedBtcNetwork = "signet" - - Btcd SupportedBtcBackend = "btcd" - Bitcoind SupportedBtcBackend = "bitcoind" ) func (c SupportedBtcNetwork) String() string { return string(c) } -func (c SupportedBtcBackend) String() string { - return string(c) -} - func GetWrappedTxs(msg *wire.MsgBlock) []*btcutil.Tx { btcTxs := []*btcutil.Tx{} @@ -53,12 +45,3 @@ func GetValidNetParams() map[string]bool { return params } - -func GetValidBtcBackends() map[SupportedBtcBackend]bool { - validBtcBackends := map[SupportedBtcBackend]bool{ - Bitcoind: true, - Btcd: true, - } - - return validBtcBackends -} From 10de19b2b9a6bb4d6d9bdfdd93441f09e9a4d01a Mon Sep 17 00:00:00 2001 From: lazar Date: Tue, 27 Aug 2024 15:39:01 +0200 Subject: [PATCH 16/24] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d2f369e9..0b64595e 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,8 @@ There are four vigilante programs: ## Requirements - Go 1.23 +- Docker - Package [libzmq](https://github.com/zeromq/libzmq) -- [btcd](https://github.com/btcsuite/btcd/tree/master?tab=readme-ov-file#installation) binaries (only for testing) ## Building From db30811e98535a6a5fe71f1a97d586554c08477e Mon Sep 17 00:00:00 2001 From: lazar Date: Tue, 27 Aug 2024 18:04:28 +0200 Subject: [PATCH 17/24] update readme --- README.md | 93 ++++++++++++++++--------------------------------------- 1 file changed, 26 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 0b64595e..218ff0c5 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ for an arbitrary number of nodes. ```shell $BABYLON_PATH/build/babylond testnet \ --v 1 \ - --output-dir $TESTNET_PATH \ + --output-dir /Users/lazar/work/test \ --starting-ip-address 192.168.10.2 \ --keyring-backend test \ --chain-id chain-test @@ -66,64 +66,44 @@ This will be later used to retrieve the certificate required for RPC connections mkdir $TESTNET_PATH/bitcoin ``` -For a Docker deployment, we want the vigilante to be able to communicate with -the Babylon and Bitcoin instances running on the local network. -We can accomplish that through the `host.docker.internal` DNS name, -which the Docker network translates to the Docker machine. -To enable Bitcoin RPC requests, we need to add the `host.docker.internal` -DNS host to the `rpc.cert` file that was created by the previous command. -To do that we use the btcd `gencerts` utility, +#### Running a Bitcoin regtest with an arbitrary mining address -```shell -gencerts -d $TESTNET_PATH/bitcoin/ -H host.docker.internal -``` - -#### Running a Bitcoin simnet with an arbitrary mining address - -Launch a simnet Bitcoin node -which listens for RPC connections at port `18556` and -stores the RPC certificate under the `$TESTNET_PATH/bitcoin` directory. -The mining address is arbitrary. +Launch a regtest Bitcoind node which listens for RPC connections at port `18443`. ```shell -btcd --simnet --rpclisten 127.0.0.1:18556 --rpcuser rpcuser --rpcpass rpcpass \ - --rpccert $TESTNET_PATH/bitcoin/rpc.cert --rpckey $TESTNET_PATH/bitcoin/rpc.key \ - --miningaddr SQqHYFTSPh8WAyJvzbAC8hoLbF12UVsE5s +bitcoind -regtest \ + -txindex \ + -rpcuser=rpcuser \ + -rpcpassword=rpcpass \ + -rpcbind=0.0.0.0:18443 \ + -zmqpubsequence=tcp://0.0.0.0:28333 \ + -datadir=/data/.bitcoin \ + ``` -#### Running a Bitcoin simnet with a wallet +#### Running a Bitcoin regtest with a wallet -Launch a simnet Bitcoin node -which listens for RPC connections at port `18556` and -stores the RPC certificate under the `$TESTNET_PATH/bitcoin` directory. +Launch a regtest Bitcoind node which listens for RPC connections at port `18443`. ```shell -btcd --simnet --rpclisten 127.0.0.1:18556 --rpcuser rpcuser --rpcpass rpcpass \ - --rpccert $TESTNET_PATH/bitcoin/rpc.cert --rpckey $TESTNET_PATH/bitcoin/rpc.key +bitcoind -regtest \ + -txindex \ + -rpcuser=rpcuser \ + -rpcpassword=rpcpass \ + -rpcbind=0.0.0.0:18443 \ + -zmqpubsequence=tcp://0.0.0.0:28333 \ + -datadir=/data/.bitcoin \ + ``` Leave this process running. -Then, create a simnet Bitcoin wallet. +Then, create a regtest Bitcoin wallet. If you want to use the default vigilante file, then give the password `walletpass`. Otherwise, make sure to edit the `vigilante.yaml` to reflect the correct password. ```shell -btcwallet --simnet -u rpcuser -P rpcpass \ - --rpccert $TESTNET_PATH/bitcoin/rpc-wallet.cert --rpckey $TESTNET_PATH/bitcoin/rpc-wallet.key \ - --cafile $TESTNET_PATH/bitcoin/rpc.cert \ - --create -``` - -The above instruction is going to prompt you for a password and going to give you the seed. -Store those securely. - -Afterwards, start the wallet service listening to port `18554`: - -```shell -btcwallet --simnet -u rpcuser -P rpcpass --rpclisten=127.0.0.1:18554 \ - --rpccert $TESTNET_PATH/bitcoin/rpc-wallet.cert --rpckey $TESTNET_PATH/bitcoin/rpc-wallet.key \ - --cafile $TESTNET_PATH/bitcoin/rpc.cert +bitcoin-cli -chain=regtest -rpcuser=rpcuser -rpcpassword=rpcpass createwallet default false false pass false true ``` Leave this process running. If you get an error that a wallet already exists and you still want @@ -131,35 +111,14 @@ to create one, delete the `wallet.db` file located in the path displayed by the Create an address that will be later used for mining. The output below is a sample one. -```shell -$ btcctl --simnet --wallet -u rpcuser -P rpcpass \ - --rpccert $TESTNET_PATH/bitcoin/rpc-wallet.cert \ - --rpcserver 127.0.0.1 getnewaddress - -SQqHYFTSPh8WAyJvzbAC8hoLbF12UVsE5s -``` - -Finally, restart the btcd service with the new address. -First, kill the `btcd` process that you started in the first step, and then: - -```shell -btcd --simnet --rpclisten 127.0.0.1:18556 --rpcuser rpcuser --rpcpass rpcpass \ - --rpccert $TESTNET_PATH/bitcoin/rpc.cert --rpckey $TESTNET_PATH/bitcoin/rpc.key \ - --miningaddr $MINING_ADDRESS -``` - -where `$MINING_ADDRESS` is the address that you got as an output in the previous command. - #### Generating BTC blocks While running this setup, one might want to generate BTC blocks. -We accomplish that through the btcd `btcctl` utility and the use +We accomplish that through the `bitcoin-cli` utility and the use of the parameters we defined above. ```shell -btcctl --simnet --wallet --rpcuser=rpcuser --rpcpass=rpcpass \ - --rpccert=$TESTNET_PATH/bitcoin/rpc-wallet.cert \ - generate $NUM_BLOCKS +bitcoin-cli -chain=regtest -rpcuser=rpcuser -rpcpassword=rpcpass -generate $NUM_BLOCKS ``` where `$NUM_BLOCKS` is the number of blocks you want to generate. @@ -241,7 +200,7 @@ cp sample-vigilante-docker.yml $TESTNET_PATH/vigilante/vigilante.yml make reporter-build ``` -Afterwards, run the above image and attach the directories +Afterward, run the above image and attach the directories that contain the configuration for Babylon, Bitcoin, and the vigilante. ```shell From f2ca5f97d6ec1d0a4a63dabc6e89d760a6b6c3e1 Mon Sep 17 00:00:00 2001 From: lazar Date: Wed, 28 Aug 2024 10:40:44 +0200 Subject: [PATCH 18/24] update readme with bitcoind cmds --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 218ff0c5..6ba95e65 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ There are four vigilante programs: ## Requirements - Go 1.23 -- Docker +- Bitcoind - Package [libzmq](https://github.com/zeromq/libzmq) ## Building From 066a52641ddd3153e2a2f76a651383f08320ee07 Mon Sep 17 00:00:00 2001 From: lazar Date: Wed, 28 Aug 2024 10:43:12 +0200 Subject: [PATCH 19/24] update readme link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ba95e65..f1ce8982 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ There are four vigilante programs: ## Requirements - Go 1.23 -- Bitcoind +- [Bitcoind](https://bitcoincore.org/en/download/) - Package [libzmq](https://github.com/zeromq/libzmq) ## Building From f74d8169dca29831d158d75b4fd7fbe3f67b6125 Mon Sep 17 00:00:00 2001 From: lazar Date: Wed, 28 Aug 2024 11:34:13 +0200 Subject: [PATCH 20/24] update bitcoind cmds --- README.md | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index f1ce8982..84d2333a 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@ There are four vigilante programs: ## Requirements -- Go 1.23 -- [Bitcoind](https://bitcoincore.org/en/download/) +- [Go 1.23](https://go.dev/dl/go1.23.0.src.tar.gz) +- [Bitcoind](https://bitcoincore.org/bin) - Package [libzmq](https://github.com/zeromq/libzmq) ## Building @@ -42,7 +42,7 @@ for an arbitrary number of nodes. ```shell $BABYLON_PATH/build/babylond testnet \ --v 1 \ - --output-dir /Users/lazar/work/test \ + --output-dir $TESTNET_PATH \ --starting-ip-address 192.168.10.2 \ --keyring-backend test \ --chain-id chain-test @@ -66,19 +66,16 @@ This will be later used to retrieve the certificate required for RPC connections mkdir $TESTNET_PATH/bitcoin ``` -#### Running a Bitcoin regtest with an arbitrary mining address +```bash +# Download Bitcoin Core binary +wget https://bitcoincore.org/bin/bitcoin-core-27.0/bitcoin-27.0-x86_64-linux-gnu.tar.gz # or choose a version depending on your os -Launch a regtest Bitcoind node which listens for RPC connections at port `18443`. - -```shell -bitcoind -regtest \ - -txindex \ - -rpcuser=rpcuser \ - -rpcpassword=rpcpass \ - -rpcbind=0.0.0.0:18443 \ - -zmqpubsequence=tcp://0.0.0.0:28333 \ - -datadir=/data/.bitcoin \ +# Extract the downloaded archive +tar -xvf bitcoin-27.0-x86_64-linux-gnu.tar.gz +# Provide execution permissions to binaries +chmod +x bitcoin-27.0/bin/bitcoind +chmod +x bitcoin-27.0/bin/bitcoin-cli ``` #### Running a Bitcoin regtest with a wallet @@ -88,8 +85,8 @@ Launch a regtest Bitcoind node which listens for RPC connections at port `18443` ```shell bitcoind -regtest \ -txindex \ - -rpcuser=rpcuser \ - -rpcpassword=rpcpass \ + -rpcuser= \ + -rpcpassword= \ -rpcbind=0.0.0.0:18443 \ -zmqpubsequence=tcp://0.0.0.0:28333 \ -datadir=/data/.bitcoin \ @@ -103,13 +100,23 @@ If you want to use the default vigilante file, then give the password `walletpas Otherwise, make sure to edit the `vigilante.yaml` to reflect the correct password. ```shell -bitcoin-cli -chain=regtest -rpcuser=rpcuser -rpcpassword=rpcpass createwallet default false false pass false true +bitcoin-cli -regtest \ + -rpcuser= \ + -rpcpassword= \ + -named createwallet \ + wallet_name="" \ + passphrase="" \ + load_on_startup=true \ + descriptors=true ``` +You can generate a btc address through the `getnewaddress` command: -Leave this process running. If you get an error that a wallet already exists and you still want -to create one, delete the `wallet.db` file located in the path displayed by the error message. - -Create an address that will be later used for mining. The output below is a sample one. +```bash +bitcoin-cli -regtest \ + -rpcuser= \ + -rpcpassword= \ + getnewaddress +``` #### Generating BTC blocks @@ -118,7 +125,10 @@ We accomplish that through the `bitcoin-cli` utility and the use of the parameters we defined above. ```shell -bitcoin-cli -chain=regtest -rpcuser=rpcuser -rpcpassword=rpcpass -generate $NUM_BLOCKS +bitcoin-cli -chain=regtest \ + -rpcuser= \ + -generate $NUM_BLOCKS ``` where `$NUM_BLOCKS` is the number of blocks you want to generate. From c8a5c3638e13a024676b143dd84b8de33041433f Mon Sep 17 00:00:00 2001 From: lazar Date: Wed, 28 Aug 2024 15:07:51 +0200 Subject: [PATCH 21/24] prevent tx1 resubmission if tx2 fails --- submitter/relayer/relayer.go | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index 1e23ebb0..666e0c3f 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -80,9 +80,15 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp return nil } - if rl.lastSubmittedCheckpoint == nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch { + if rl.lastSubmittedCheckpoint == nil || + rl.lastSubmittedCheckpoint.Tx1 == nil || + rl.lastSubmittedCheckpoint.Epoch < ckptEpoch { rl.logger.Infof("Submitting a raw checkpoint for epoch %v for the first time", ckptEpoch) + if rl.lastSubmittedCheckpoint == nil { + rl.lastSubmittedCheckpoint = &types.CheckpointInfo{} + } + submittedCheckpoint, err := rl.convertCkptToTwoTxAndSubmit(ckpt.Ckpt) if err != nil { return err @@ -295,14 +301,21 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxInfo, *types.BtcTxInfo, error) { // recipient is a change address that all the // remaining balance of the utxo is sent to - tx1, err := rl.buildTxWithData(data1, nil) - if err != nil { - return nil, nil, fmt.Errorf("failed to add data to tx1: %w", err) - } + tx1 := rl.lastSubmittedCheckpoint.Tx1 + // prevent resending tx1 if it was successful + if rl.lastSubmittedCheckpoint.Tx1 == nil { + var err error + tx1, err = rl.buildTxWithData(data1, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to add data to tx1: %w", err) + } - tx1.TxId, err = rl.sendTxToBTC(tx1.Tx) - if err != nil { - return nil, nil, fmt.Errorf("failed to send tx1 to BTC: %w", err) + tx1.TxId, err = rl.sendTxToBTC(tx1.Tx) + if err != nil { + return nil, nil, fmt.Errorf("failed to send tx1 to BTC: %w", err) + } + // cache a successful tx1 submission + rl.lastSubmittedCheckpoint.Tx1 = tx1 } // the second tx consumes the second output (index 1) @@ -317,8 +330,6 @@ func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxIn return nil, nil, fmt.Errorf("failed to send tx2 to BTC: %w", err) } - // TODO: if tx1 succeeds but tx2 fails, we should not resent tx1 - return tx1, tx2, nil } From d90117d6e79ada7863423b05dbdd2dffd8522814 Mon Sep 17 00:00:00 2001 From: lazar Date: Fri, 30 Aug 2024 17:41:43 +0200 Subject: [PATCH 22/24] cleaner code --- submitter/relayer/relayer.go | 37 ++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index 666e0c3f..ce8eaec1 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -301,33 +301,38 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxInfo, *types.BtcTxInfo, error) { // recipient is a change address that all the // remaining balance of the utxo is sent to + + // helper function to build and send a transaction + buildAndSendTx := func(data []byte, parentTx *wire.MsgTx) (*types.BtcTxInfo, error) { + tx, err := rl.buildTxWithData(data, parentTx) + if err != nil { + return nil, fmt.Errorf("failed to add data to tx: %w", err) + } + + tx.TxId, err = rl.sendTxToBTC(tx.Tx) + if err != nil { + return nil, fmt.Errorf("failed to send tx to BTC: %w", err) + } + + return tx, nil + } + tx1 := rl.lastSubmittedCheckpoint.Tx1 // prevent resending tx1 if it was successful if rl.lastSubmittedCheckpoint.Tx1 == nil { var err error - tx1, err = rl.buildTxWithData(data1, nil) + tx1, err = buildAndSendTx(data1, nil) if err != nil { - return nil, nil, fmt.Errorf("failed to add data to tx1: %w", err) + return nil, nil, err } - tx1.TxId, err = rl.sendTxToBTC(tx1.Tx) - if err != nil { - return nil, nil, fmt.Errorf("failed to send tx1 to BTC: %w", err) - } - // cache a successful tx1 submission rl.lastSubmittedCheckpoint.Tx1 = tx1 } - // the second tx consumes the second output (index 1) - // of the first tx, as the output at index 0 is OP_RETURN - tx2, err := rl.buildTxWithData(data2, tx1.Tx) - if err != nil { - return nil, nil, fmt.Errorf("failed to add data to tx2: %w", err) - } - - tx2.TxId, err = rl.sendTxToBTC(tx2.Tx) + // Build and send tx2, using tx1 as the parent + tx2, err := buildAndSendTx(data2, tx1.Tx) if err != nil { - return nil, nil, fmt.Errorf("failed to send tx2 to BTC: %w", err) + return nil, nil, err } return tx1, tx2, nil From c773f7d6317f3bf2e0f89394662bfae79ae15dc0 Mon Sep 17 00:00:00 2001 From: lazar Date: Mon, 2 Sep 2024 13:01:01 +0200 Subject: [PATCH 23/24] code and readibility cleanup --- submitter/relayer/relayer.go | 159 ++++++++++++++++++++++++----------- 1 file changed, 108 insertions(+), 51 deletions(-) diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index ce8eaec1..7b78f17f 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -55,14 +55,15 @@ func New( ) *Relayer { metrics.ResendIntervalSecondsGauge.Set(float64(config.ResendIntervalSeconds)) return &Relayer{ - Estimator: est, - BTCWallet: wallet, - tag: tag, - version: version, - submitterAddress: submitterAddress, - metrics: metrics, - config: config, - logger: parentLogger.With(zap.String("module", "relayer")).Sugar(), + Estimator: est, + BTCWallet: wallet, + tag: tag, + version: version, + submitterAddress: submitterAddress, + metrics: metrics, + config: config, + lastSubmittedCheckpoint: &types.CheckpointInfo{}, + logger: parentLogger.With(zap.String("module", "relayer")).Sugar(), } } @@ -80,16 +81,27 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp return nil } - if rl.lastSubmittedCheckpoint == nil || - rl.lastSubmittedCheckpoint.Tx1 == nil || - rl.lastSubmittedCheckpoint.Epoch < ckptEpoch { + sendCompleteCkpt := rl.lastSubmittedCheckpoint.Tx1 == nil || + rl.lastSubmittedCheckpoint.Epoch < ckptEpoch + + // we want to avoid resending tx1 if only tx2 submission has failed + shouldSendTx2 := (rl.lastSubmittedCheckpoint.Tx1 != nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch) && + rl.lastSubmittedCheckpoint.Tx2 == nil + + if sendCompleteCkpt { rl.logger.Infof("Submitting a raw checkpoint for epoch %v for the first time", ckptEpoch) - if rl.lastSubmittedCheckpoint == nil { - rl.lastSubmittedCheckpoint = &types.CheckpointInfo{} + submittedCheckpoint, err := rl.convertCkptToTwoTxAndSubmit(ckpt.Ckpt) + if err != nil { + return err } - submittedCheckpoint, err := rl.convertCkptToTwoTxAndSubmit(ckpt.Ckpt) + rl.lastSubmittedCheckpoint = submittedCheckpoint + + return nil + } else if shouldSendTx2 { + rl.logger.Infof("Retrying to send tx2 for epoch %v, tx1 %s", ckptEpoch, rl.lastSubmittedCheckpoint.Tx1.TxId) + submittedCheckpoint, err := rl.retrySendTx2(ckpt.Ckpt) if err != nil { return err } @@ -244,49 +256,72 @@ func (rl *Relayer) signTx(tx *wire.MsgTx) (*wire.MsgTx, error) { return signedTx, nil } -func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResponse) (*types.CheckpointInfo, error) { +func (rl *Relayer) encodeCheckpointData(ckpt *ckpttypes.RawCheckpointResponse) ([]byte, []byte, error) { + // Convert to raw checkpoint rawCkpt, err := ckpt.ToRawCheckpoint() if err != nil { - return nil, err + return nil, nil, err } + + // Convert raw checkpoint to BTC checkpoint btcCkpt, err := ckpttypes.FromRawCkptToBTCCkpt(rawCkpt, rl.submitterAddress) if err != nil { - return nil, err + return nil, nil, err } + + // Encode checkpoint data data1, data2, err := btctxformatter.EncodeCheckpointData( rl.tag, rl.version, btcCkpt, ) if err != nil { - return nil, err + return nil, nil, err } - tx1, tx2, err := rl.ChainTwoTxAndSend(data1, data2) - if err != nil { - return nil, err - } + // Return the encoded data + return data1, data2, nil +} +func (rl *Relayer) logAndRecordCheckpointMetrics(tx1, tx2 *types.BtcTxInfo, epochNum uint64) { // this is to wait for btcwallet to update utxo database so that // the tx that tx1 consumes will not appear in the next unspent txs lit + // todo(Lazar): is the arbitrary timeout here necessary? time.Sleep(1 * time.Second) + // Log the transactions sent for checkpointing rl.logger.Infof("Sent two txs to BTC for checkpointing epoch %v, first txid: %s, second txid: %s", - ckpt.EpochNum, tx1.Tx.TxHash().String(), tx2.Tx.TxHash().String()) + epochNum, tx1.Tx.TxHash().String(), tx2.Tx.TxHash().String()) - // record metrics of the two transactions + // Record metrics for the first transaction rl.metrics.NewSubmittedCheckpointSegmentGaugeVec.WithLabelValues( - strconv.Itoa(int(ckpt.EpochNum)), + strconv.Itoa(int(epochNum)), "0", tx1.Tx.TxHash().String(), strconv.Itoa(int(tx1.Fee)), ).SetToCurrentTime() + + // Record metrics for the second transaction rl.metrics.NewSubmittedCheckpointSegmentGaugeVec.WithLabelValues( - strconv.Itoa(int(ckpt.EpochNum)), + strconv.Itoa(int(epochNum)), "1", tx2.Tx.TxHash().String(), strconv.Itoa(int(tx2.Fee)), ).SetToCurrentTime() +} + +func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResponse) (*types.CheckpointInfo, error) { + data1, data2, err := rl.encodeCheckpointData(ckpt) + if err != nil { + return nil, err + } + + tx1, tx2, err := rl.ChainTwoTxAndSend(data1, data2) + if err != nil { + return nil, err + } + + rl.logAndRecordCheckpointMetrics(tx1, tx2, ckpt.EpochNum) return &types.CheckpointInfo{ Epoch: ckpt.EpochNum, @@ -296,41 +331,63 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp }, nil } +func (rl *Relayer) retrySendTx2(ckpt *ckpttypes.RawCheckpointResponse) (*types.CheckpointInfo, error) { + _, data2, err := rl.encodeCheckpointData(ckpt) + if err != nil { + return nil, err + } + + tx1 := rl.lastSubmittedCheckpoint.Tx1 + if tx1 == nil { + return nil, fmt.Errorf("tx1 is nil") // shouldn't happen, sanity check + } + + tx2, err := rl.buildAndSendTx(data2, tx1.Tx) + if err != nil { + return nil, err + } + + rl.logAndRecordCheckpointMetrics(tx1, tx2, ckpt.EpochNum) + + return &types.CheckpointInfo{ + Epoch: ckpt.EpochNum, + Ts: time.Now(), + Tx1: tx1, + Tx2: tx2, + }, nil +} + +// buildAndSendTx helper function to build and send a transaction +func (rl *Relayer) buildAndSendTx(data []byte, parentTx *wire.MsgTx) (*types.BtcTxInfo, error) { + tx, err := rl.buildTxWithData(data, parentTx) + if err != nil { + return nil, fmt.Errorf("failed to add data to tx: %w", err) + } + + tx.TxId, err = rl.sendTxToBTC(tx.Tx) + if err != nil { + return nil, fmt.Errorf("failed to send tx to BTC: %w", err) + } + + return tx, nil +} + // ChainTwoTxAndSend builds two chaining txs with the given data: // the second tx consumes the output of the first tx func (rl *Relayer) ChainTwoTxAndSend(data1 []byte, data2 []byte) (*types.BtcTxInfo, *types.BtcTxInfo, error) { // recipient is a change address that all the // remaining balance of the utxo is sent to - // helper function to build and send a transaction - buildAndSendTx := func(data []byte, parentTx *wire.MsgTx) (*types.BtcTxInfo, error) { - tx, err := rl.buildTxWithData(data, parentTx) - if err != nil { - return nil, fmt.Errorf("failed to add data to tx: %w", err) - } - - tx.TxId, err = rl.sendTxToBTC(tx.Tx) - if err != nil { - return nil, fmt.Errorf("failed to send tx to BTC: %w", err) - } - - return tx, nil + tx1, err := rl.buildAndSendTx(data1, nil) + if err != nil { + return nil, nil, err } - tx1 := rl.lastSubmittedCheckpoint.Tx1 - // prevent resending tx1 if it was successful - if rl.lastSubmittedCheckpoint.Tx1 == nil { - var err error - tx1, err = buildAndSendTx(data1, nil) - if err != nil { - return nil, nil, err - } - - rl.lastSubmittedCheckpoint.Tx1 = tx1 - } + // cache the success of tx1, we need it if we fail with tx2 send + rl.lastSubmittedCheckpoint.Tx1 = tx1 // Build and send tx2, using tx1 as the parent - tx2, err := buildAndSendTx(data2, tx1.Tx) + tx2, err := rl.buildAndSendTx(data2, tx1.Tx) if err != nil { return nil, nil, err } From a6a0da0663076dd0510a59cbf2d06d0bdb8f836a Mon Sep 17 00:00:00 2001 From: lazar Date: Mon, 2 Sep 2024 14:39:44 +0200 Subject: [PATCH 24/24] more cleanup --- submitter/relayer/relayer.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/submitter/relayer/relayer.go b/submitter/relayer/relayer.go index 7b78f17f..4d167935 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -81,15 +81,8 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp return nil } - sendCompleteCkpt := rl.lastSubmittedCheckpoint.Tx1 == nil || - rl.lastSubmittedCheckpoint.Epoch < ckptEpoch - - // we want to avoid resending tx1 if only tx2 submission has failed - shouldSendTx2 := (rl.lastSubmittedCheckpoint.Tx1 != nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch) && - rl.lastSubmittedCheckpoint.Tx2 == nil - - if sendCompleteCkpt { - rl.logger.Infof("Submitting a raw checkpoint for epoch %v for the first time", ckptEpoch) + if rl.shouldSendCompleteCkpt(ckptEpoch) { + rl.logger.Infof("Submitting a raw checkpoint for epoch %v", ckptEpoch) submittedCheckpoint, err := rl.convertCkptToTwoTxAndSubmit(ckpt.Ckpt) if err != nil { @@ -99,7 +92,7 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp rl.lastSubmittedCheckpoint = submittedCheckpoint return nil - } else if shouldSendTx2 { + } else if rl.shouldSendTx2(ckptEpoch) { rl.logger.Infof("Retrying to send tx2 for epoch %v, tx1 %s", ckptEpoch, rl.lastSubmittedCheckpoint.Tx1.TxId) submittedCheckpoint, err := rl.retrySendTx2(ckpt.Ckpt) if err != nil { @@ -162,6 +155,16 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp return nil } +func (rl *Relayer) shouldSendCompleteCkpt(ckptEpoch uint64) bool { + return rl.lastSubmittedCheckpoint.Tx1 == nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch +} + +// shouldSendTx2 - we want to avoid resending tx1 if only tx2 submission has failed +func (rl *Relayer) shouldSendTx2(ckptEpoch uint64) bool { + return (rl.lastSubmittedCheckpoint.Tx1 != nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch) && + rl.lastSubmittedCheckpoint.Tx2 == nil +} + // shouldResendCheckpoint checks whether the bumpedFee is effective for replacement func (rl *Relayer) shouldResendCheckpoint(ckptInfo *types.CheckpointInfo, bumpedFee btcutil.Amount) bool { // if the bumped fee is less than the fee of the previous second tx plus the minimum required bumping fee @@ -284,11 +287,6 @@ func (rl *Relayer) encodeCheckpointData(ckpt *ckpttypes.RawCheckpointResponse) ( } func (rl *Relayer) logAndRecordCheckpointMetrics(tx1, tx2 *types.BtcTxInfo, epochNum uint64) { - // this is to wait for btcwallet to update utxo database so that - // the tx that tx1 consumes will not appear in the next unspent txs lit - // todo(Lazar): is the arbitrary timeout here necessary? - time.Sleep(1 * time.Second) - // Log the transactions sent for checkpointing rl.logger.Infof("Sent two txs to BTC for checkpointing epoch %v, first txid: %s, second txid: %s", epochNum, tx1.Tx.TxHash().String(), tx2.Tx.TxHash().String()) @@ -331,6 +329,8 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp }, nil } +// retrySendTx2 - rebuilds the tx2 and sends it, expects that tx1 has been sent and +// lastSubmittedCheckpoint.Tx1 is not nil func (rl *Relayer) retrySendTx2(ckpt *ckpttypes.RawCheckpointResponse) (*types.CheckpointInfo, error) { _, data2, err := rl.encodeCheckpointData(ckpt) if err != nil {