Skip to content

Commit

Permalink
feat(monitor): bootstrap monitor from db (#34)
Browse files Browse the repository at this point in the history
This PR enables the monitor to start from the last processed confirmed
header, we also save the epoch

References
[issue](https://github.com/babylonchain/vigilante/issues/156).
  • Loading branch information
Lazar955 authored Sep 10, 2024
1 parent 3b8378d commit 3d6f6b6
Show file tree
Hide file tree
Showing 23 changed files with 585 additions and 79 deletions.
8 changes: 7 additions & 1 deletion cmd/vigilante/cmd/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ func GetMonitorCmd() *cobra.Command {
panic(err)
}

dbBackend, err := cfg.Monitor.DatabaseConfig.GetDbBackend()
if err != nil {
panic(err)
}

// create monitor
vigilanteMonitor, err = monitor.New(
&cfg.Monitor,
Expand All @@ -89,6 +94,7 @@ func GetMonitorCmd() *cobra.Command {
btcClient,
btcNotifier,
monitorMetrics,
dbBackend,
)
if err != nil {
panic(fmt.Errorf("failed to create vigilante monitor: %w", err))
Expand All @@ -100,7 +106,7 @@ func GetMonitorCmd() *cobra.Command {
}

// start
go vigilanteMonitor.Start()
go vigilanteMonitor.Start(genesisInfo.GetBaseBTCHeight())

// start RPC server
server.Start()
Expand Down
2 changes: 1 addition & 1 deletion config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
const (
defaultRetrySleepTime = 5 * time.Second
defaultMaxRetrySleepTime = 5 * time.Minute
defaultMaxRetryTimes = 5
defaultMaxRetryTimes = 25
)

// CommonConfig defines the server's basic configuration
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (

const (
defaultConfigFilename = "vigilante.yml"
defaultDataDirname = "data"
)

var (
Expand All @@ -24,6 +25,10 @@ var (
defaultRPCCertFile = filepath.Join(defaultAppDataDir, "rpc.cert")
)

func DataDir(homePath string) string {
return filepath.Join(homePath, defaultDataDirname)
}

// Config defines the server's top level configuration
type Config struct {
Common CommonConfig `mapstructure:"common"`
Expand Down
82 changes: 82 additions & 0 deletions config/dbconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package config

import (
"fmt"
"github.com/lightningnetwork/lnd/kvdb"
"time"
)

const (
defaultDbName = "vigilante.db"
)

type DBConfig struct {
// DBPath is the directory path in which the database file should be
// stored.
DBPath string `long:"dbpath" description:"The directory path in which the database file should be stored."`

// DBFileName is the name of the database file.
DBFileName string `long:"dbfilename" description:"The name of the database file."`

// NoFreelistSync, if true, prevents the database from syncing its
// freelist to disk, resulting in improved performance at the expense of
// increased startup time.
NoFreelistSync bool `long:"nofreelistsync" description:"Prevents the database from syncing its freelist to disk, resulting in improved performance at the expense of increased startup time."`

// AutoCompact specifies if a Bolt based database backend should be
// automatically compacted on startup (if the minimum age of the
// database file is reached). This will require additional disk space
// for the compacted copy of the database but will result in an overall
// lower database size after the compaction.
AutoCompact bool `long:"autocompact" description:"Specifies if a Bolt based database backend should be automatically compacted on startup (if the minimum age of the database file is reached). This will require additional disk space for the compacted copy of the database but will result in an overall lower database size after the compaction."`

// AutoCompactMinAge specifies the minimum time that must have passed
// since a bolt database file was last compacted for the compaction to
// be considered again.
AutoCompactMinAge time.Duration `long:"autocompactminage" description:"Specifies the minimum time that must have passed since a bolt database file was last compacted for the compaction to be considered again."`

// DBTimeout specifies the timeout value to use when opening the wallet
// database.
DBTimeout time.Duration `long:"dbtimeout" description:"Specifies the timeout value to use when opening the wallet database."`
}

func DefaultDBConfig() *DBConfig {
return DefaultDBConfigWithHomePath(defaultAppDataDir)
}

func DefaultDBConfigWithHomePath(homePath string) *DBConfig {
return &DBConfig{
DBPath: DataDir(homePath),
DBFileName: defaultDbName,
NoFreelistSync: true,
AutoCompact: false,
AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
DBTimeout: kvdb.DefaultDBTimeout,
}
}

func (cfg *DBConfig) ToBoltBackendConfig() *kvdb.BoltBackendConfig {
return &kvdb.BoltBackendConfig{
DBPath: cfg.DBPath,
DBFileName: cfg.DBFileName,
NoFreelistSync: cfg.NoFreelistSync,
AutoCompact: cfg.AutoCompact,
AutoCompactMinAge: cfg.AutoCompactMinAge,
DBTimeout: cfg.DBTimeout,
}
}

func (cfg *DBConfig) Validate() error {
if cfg.DBPath == "" {
return fmt.Errorf("DB path cannot be empty")
}

if cfg.DBFileName == "" {
return fmt.Errorf("DB file name cannot be empty")
}
return nil
}

func (cfg *DBConfig) GetDbBackend() (kvdb.Backend, error) {
return kvdb.GetBoltBackend(cfg.ToBoltBackendConfig())
}
2 changes: 2 additions & 0 deletions config/monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ type MonitorConfig struct {
BtcConfirmationDepth uint64 `mapstructure:"btc-confirmation-depth"`
// whether to enable liveness checker
EnableLivenessChecker bool `mapstructure:"enable-liveness-checker"`

DatabaseConfig *DBConfig `mapstructure:"database-config"`
}

func (cfg *MonitorConfig) Validate() error {
Expand Down
4 changes: 2 additions & 2 deletions e2etest/atomicslasher_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestAtomicSlasher(t *testing.T) {
// segwit is activated at height 300. It's needed by staking/slashing tx
numMatureOutputs := uint32(300)

tm := StartManager(t, numMatureOutputs)
tm := StartManager(t, numMatureOutputs, defaultEpochInterval)
defer tm.Stop(t)

// start WebSocket connection with Babylon for subscriber services
Expand Down Expand Up @@ -143,7 +143,7 @@ func TestAtomicSlasher_Unbonding(t *testing.T) {
// segwit is activated at height 300. It's needed by staking/slashing tx
numMatureOutputs := uint32(300)

tm := StartManager(t, numMatureOutputs)
tm := StartManager(t, numMatureOutputs, defaultEpochInterval)
defer tm.Stop(t)

// start WebSocket connection with Babylon for subscriber services
Expand Down
3 changes: 2 additions & 1 deletion e2etest/babylon_node_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ type BabylonNodeHandler struct {
babylonNode *babylonNode
}

func NewBabylonNodeHandler(baseHeaderHex string, slashingAddress string) (*BabylonNodeHandler, error) {
func NewBabylonNodeHandler(baseHeaderHex string, slashingAddress string, epochInterval uint) (*BabylonNodeHandler, error) {
testDir, err := baseDirBabylondir()
if err != nil {
return nil, err
Expand All @@ -141,6 +141,7 @@ func NewBabylonNodeHandler(baseHeaderHex string, slashingAddress string) (*Babyl
"--btc-confirmation-depth=2",
"--additional-sender-account",
"--btc-network=regtest",
fmt.Sprintf("--epoch-interval=%d", epochInterval),
fmt.Sprintf("--slashing-address=%s", slashingAddress),
fmt.Sprintf("--btc-base-header=%s", baseHeaderHex),
"--covenant-quorum=1",
Expand Down
3 changes: 1 addition & 2 deletions e2etest/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (m *Manager) ExecCmd(t *testing.T, containerName string, command []string)
errBuf bytes.Buffer
)

timeout := 20 * time.Second
timeout := 120 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

Expand Down Expand Up @@ -150,7 +150,6 @@ func (m *Manager) RunBitcoindResource(
"18444/tcp": {{HostIP: "", HostPort: "18444"}},
},
Cmd: []string{
"-debug=1",
"-regtest",
"-txindex",
"-rpcuser=user",
Expand Down
147 changes: 147 additions & 0 deletions e2etest/monitor_e2e_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//go:build e2e
// +build e2e

package e2etest

import (
"fmt"
bbnclient "github.com/babylonlabs-io/babylon/client/client"
"github.com/babylonlabs-io/vigilante/btcclient"
"github.com/babylonlabs-io/vigilante/metrics"
"github.com/babylonlabs-io/vigilante/monitor"
"github.com/babylonlabs-io/vigilante/reporter"
"github.com/babylonlabs-io/vigilante/submitter"
"github.com/babylonlabs-io/vigilante/testutil"
"github.com/babylonlabs-io/vigilante/types"
"github.com/btcsuite/btcd/chaincfg"
sdk "github.com/cosmos/cosmos-sdk/types"
promtestutil "github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"time"

"testing"
)

// TestMonitorBootstrap - validates that after a restart monitor bootstraps from DB
func TestMonitorBootstrap(t *testing.T) {
numMatureOutputs := uint32(150)

tm := StartManager(t, numMatureOutputs, 2)
defer tm.Stop(t)

backend, err := btcclient.NewNodeBackend(
btcclient.ToBitcoindConfig(tm.Config.BTC),
&chaincfg.RegressionNetParams,
&btcclient.EmptyHintCache{},
)
require.NoError(t, err)

err = backend.Start()
require.NoError(t, err)

dbBackend := testutil.MakeTestBackend(t)

monitorMetrics := metrics.NewMonitorMetrics()
genesisPath := fmt.Sprintf("%s/config/genesis.json", tm.Config.Babylon.KeyDirectory)
genesisInfo, err := types.GetGenesisInfoFromFile(genesisPath)
require.NoError(t, err)

tm.Config.Submitter.PollingIntervalSeconds = 1
subAddr, _ := sdk.AccAddressFromBech32(submitterAddrStr)

// create submitter
vigilantSubmitter, _ := submitter.New(
&tm.Config.Submitter,
logger,
tm.BTCClient,
tm.BabylonClient,
subAddr,
tm.Config.Common.RetrySleepTime,
tm.Config.Common.MaxRetrySleepTime,
tm.Config.Common.MaxRetryTimes,
metrics.NewSubmitterMetrics(),
)

vigilantSubmitter.Start()
defer vigilantSubmitter.Stop()

vigilantReporter, err := reporter.New(
&tm.Config.Reporter,
logger,
tm.BTCClient,
tm.BabylonClient,
backend,
tm.Config.Common.RetrySleepTime,
tm.Config.Common.MaxRetrySleepTime,
metrics.NewReporterMetrics(),
)
require.NoError(t, err)

defer func() {
vigilantSubmitter.Stop()
vigilantSubmitter.WaitForShutdown()
}()

mon, err := monitor.New(
&tm.Config.Monitor,
&tm.Config.Common,
zap.NewNop(),
genesisInfo,
tm.BabylonClient,
tm.BTCClient,
backend,
monitorMetrics,
dbBackend,
)
require.NoError(t, err)
vigilantReporter.Start()
defer vigilantReporter.Stop()

go func() {
ticker := time.NewTicker(3 * time.Second)
timer := time.NewTimer(15 * time.Second)
defer timer.Stop()
defer ticker.Stop()
for {
select {
case <-ticker.C:
tm.mineBlock(t)
case <-timer.C:
return
}
}
}()

go mon.Start(genesisInfo.GetBaseBTCHeight())

time.Sleep(15 * time.Second)
mon.Stop()

// use a new bbn client
babylonClient, err := bbnclient.New(&tm.Config.Babylon, nil)
require.NoError(t, err)
defer babylonClient.Stop()

mon, err = monitor.New(
&tm.Config.Monitor,
&tm.Config.Common,
zap.NewNop(),
genesisInfo,
babylonClient,
tm.BTCClient,
backend,
monitorMetrics,
dbBackend,
)
require.NoError(t, err)
go mon.Start(genesisInfo.GetBaseBTCHeight())

defer mon.Stop()

require.Zero(t, promtestutil.ToFloat64(mon.Metrics().InvalidBTCHeadersCounter))
require.Zero(t, promtestutil.ToFloat64(mon.Metrics().InvalidEpochsCounter))
require.Eventually(t, func() bool {
return mon.BTCScanner.GetBaseHeight() > genesisInfo.GetBaseBTCHeight()
}, eventuallyWaitTimeOut, eventuallyPollTime)
}
6 changes: 3 additions & 3 deletions e2etest/reporter_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) {
// no need to much mature outputs, we are not going to submit transactions in this test
numMatureOutputs := uint32(150)

tm := StartManager(t, numMatureOutputs)
tm := StartManager(t, numMatureOutputs, defaultEpochInterval)
defer tm.Stop(t)

reporterMetrics := metrics.NewReporterMetrics()
Expand Down Expand Up @@ -120,7 +120,7 @@ func TestRelayHeadersAndHandleRollbacks(t *testing.T) {
// no need to much mature outputs, we are not going to submit transactions in this test
numMatureOutputs := uint32(150)

tm := StartManager(t, numMatureOutputs)
tm := StartManager(t, numMatureOutputs, defaultEpochInterval)
// this is necessary to receive notifications about new transactions entering mempool
defer tm.Stop(t)

Expand Down Expand Up @@ -171,7 +171,7 @@ func TestHandleReorgAfterRestart(t *testing.T) {
// no need to much mature outputs, we are not going to submit transactions in this test
numMatureOutputs := uint32(150)

tm := StartManager(t, numMatureOutputs)
tm := StartManager(t, numMatureOutputs, defaultEpochInterval)
// this is necessary to receive notifications about new transactions entering mempool
defer tm.Stop(t)

Expand Down
Loading

0 comments on commit 3d6f6b6

Please sign in to comment.