diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 7f90a64e..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,105 +0,0 @@ -version: 2.1 - -orbs: - go: circleci/go@1.9.0 - aws-ecr: circleci/aws-ecr@8.2.1 - -jobs: - build_lint_test: - machine: - image: ubuntu-2204:2022.10.1 - resource_class: large - steps: - - go/install: - version: "1.21.4" - - checkout - - run: - name: Print Go environment - command: "go env" - - go/load-cache: - key: go-mod-v6-{{ checksum "go.sum" }} - - go/mod-download - - run: - name: Install Dependencies - command: sudo apt-get install libzmq3-dev - - go/save-cache: - key: go-mod-v6-{{ checksum "go.sum" }} - path: "/home/circleci/.go_workspace/pkg/mod" - - run: - name: Build vigilante - command: make build - - run: - name: Lint - command: | - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.55.2 - ./bin/golangci-lint run - - run: - name: Run tests - command: | - make test - - run: - name: Run integration tests - command: | - make test-e2e - - build_docker: - machine: - image: ubuntu-2204:2022.10.1 - resource_class: large - steps: - - checkout - - aws-ecr/build-image: - push-image: false - dockerfile: Dockerfile - path: ./ - build-path: ./ - tag: "$CIRCLE_SHA1,$CIRCLE_TAG" - repo: "vigilante" - - run: - name: Save Docker image to export it to workspace - command: | - docker save $(docker image ls --format '{{.Repository}}:{{.Tag}}') > /tmp/vigilante.tar - - persist_to_workspace: - root: /tmp - paths: - - vigilante.tar - - push_docker: - machine: - image: ubuntu-2204:2022.10.1 - resource_class: large - steps: - - attach_workspace: - at: /tmp - - run: - name: Load Docker image from workspace - command: | - docker load -i /tmp/vigilante.tar - - aws-ecr/ecr-login: - aws-access-key-id: AWS_ACCESS_KEY_ID - aws-secret-access-key: AWS_SECRET_ACCESS_KEY - region: "$AWS_REGION" - - aws-ecr/push-image: - registry-id: AWS_ECR_REGISTRY_ID - region: "$AWS_REGION" - repo: "vigilante" - tag: "$CIRCLE_SHA1,$CIRCLE_TAG" - -workflows: - CI: - jobs: - - build_lint_test - - build_docker: - filters: - tags: - only: /.*/ - - push_docker: - requires: - - build_docker - filters: - tags: - only: /.*/ - branches: - only: - - main - - dev diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ea7652c1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: ci + +on: + pull_request: + branches: + - '**' + +jobs: + lint_test: + uses: babylonlabs-io/.github/.github/workflows/reusable_go_lint_test.yml@v0.3.1 + with: + go-version: '1.23' + go-lint-version: 'v1.60.2' + run-unit-tests: true + run-integration-tests: true + run-lint: true + install-dependencies-command: | + sudo apt-get update + sudo apt-get install -y libzmq3-dev + + docker_pipeline: + uses: babylonlabs-io/.github/.github/workflows/reusable_docker_pipeline.yml@v0.3.1 + secrets: inherit + with: + publish: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 00000000..c7e1dce4 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,29 @@ +name: docker_publish + +on: + push: + branches: + - 'main' + tags: + - '*' + +jobs: + lint_test: + uses: babylonlabs-io/.github/.github/workflows/reusable_go_lint_test.yml@v0.3.1 + with: + go-version: '1.23' + go-lint-version: 'v1.60.2' + run-unit-tests: true + run-integration-tests: true + run-lint: true + install-dependencies-command: | + sudo apt-get update + sudo apt-get install -y libzmq3-dev + + + docker_pipeline: + needs: ["lint_test"] + uses: babylonlabs-io/.github/.github/workflows/reusable_docker_pipeline.yml@v0.3.1 + secrets: inherit + with: + publish: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1e327011 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,5 @@ +# Contributing + +Vigilante repository follows the same contributing rules as +[Babylon node](https://github.com/babylonlabs-io/babylon/blob/main/CONTRIBUTING.md) +repository. \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 449db259..dd8a3271 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ ## Image for building -FROM golang:1.21-alpine AS build-env +FROM golang:1.23-alpine AS build-env # TARGETPLATFORM should be one of linux/amd64 or linux/arm64. diff --git a/Makefile b/Makefile index ad9d324d..dd9d0f8b 100644 --- a/Makefile +++ b/Makefile @@ -6,12 +6,9 @@ MOCKGEN_CMD=go run ${MOCKGEN_REPO}@${MOCKGEN_VERSION} BUILDDIR ?= $(CURDIR)/build TOOLS_DIR := tools -BTCD_PKG := github.com/btcsuite/btcd -BTCDW_PKG := github.com/btcsuite/btcwallet BABYLON_PKG := github.com/babylonlabs-io/babylon/cmd/babylond GO_BIN := ${GOPATH}/bin -BTCD_BIN := $(GO_BIN)/btcd ldflags := $(LDFLAGS) build_tags := $(BUILD_TAGS) @@ -52,7 +49,7 @@ test: go test ./... test-e2e: - cd $(TOOLS_DIR); go install -trimpath $(BTCD_PKG); go install -trimpath $(BTCDW_PKG); go install -trimpath $(BABYLON_PKG); + cd $(TOOLS_DIR); go install -trimpath $(BABYLON_PKG); go test -mod=readonly -timeout=25m -v $(PACKAGES_E2E) -count=1 --tags=e2e build-docker: diff --git a/README.md b/README.md index 5eb1348d..92d81b1a 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ There are four vigilante programs: ## Requirements -- Go 1.21 +- [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) -- [btcd](https://github.com/btcsuite/btcd/tree/master?tab=readme-ov-file#installation) binaries (only for testing) ## Building @@ -66,100 +66,69 @@ 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, +```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 -```shell -gencerts -d $TESTNET_PATH/bitcoin/ -H host.docker.internal -``` - -#### Running a Bitcoin simnet with an arbitrary mining address +# Extract the downloaded archive +tar -xvf bitcoin-27.0-x86_64-linux-gnu.tar.gz -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. - -```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 +# Provide execution permissions to binaries +chmod +x bitcoin-27.0/bin/bitcoind +chmod +x bitcoin-27.0/bin/bitcoin-cli ``` -#### 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= \ + -rpcpassword= \ + -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 +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: -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 +```bash +bitcoin-cli -regtest \ + -rpcuser= \ + -rpcpassword= \ + getnewaddress ``` -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. - -```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= \ + -rpcpassword= \ + -generate $NUM_BLOCKS ``` where `$NUM_BLOCKS` is the number of blocks you want to generate. @@ -241,7 +210,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 diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md new file mode 100644 index 00000000..61014695 --- /dev/null +++ b/RELEASE_PROCESS.md @@ -0,0 +1,5 @@ +# Release Process + +Vigilate repository follows the same release process rules as +[Babylon node](https://github.com/babylonlabs-io/babylon/blob/main/RELEASE_PROCESS.md) +repository. \ No newline at end of file diff --git a/btcclient/client.go b/btcclient/client.go index b784fd9b..8da20dcd 100644 --- a/btcclient/client.go +++ b/btcclient/client.go @@ -15,8 +15,6 @@ import ( "go.uber.org/zap" "github.com/babylonlabs-io/vigilante/config" - "github.com/babylonlabs-io/vigilante/types" - "github.com/babylonlabs-io/vigilante/zmq" ) var _ BTCClient = &Client{} @@ -25,18 +23,14 @@ 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 + 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) { @@ -54,10 +48,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) - } } diff --git a/btcclient/client_block_subscriber.go b/btcclient/client_block_subscriber.go deleted file mode 100644 index f0926a01..00000000 --- a/btcclient/client_block_subscriber.go +++ /dev/null @@ -1,148 +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 - } - - // ensure we are using bitcoind as Bitcoin node, as zmq is only supported by bitcoind - backend, err := rpcClient.BackendVersion() - if err != nil { - return nil, fmt.Errorf("failed to get BTC backend: %v", err) - } - if backend != rpcclient.BitcoindPost25 { - return nil, fmt.Errorf("zmq is only supported by bitcoind, but got %v", backend) - } - - 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/client_wallet.go b/btcclient/client_wallet.go index 1504eacc..fef4835d 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 @@ -26,42 +25,25 @@ 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{} - 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: true, } 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 @@ -69,15 +51,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 +67,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) { @@ -137,3 +119,11 @@ func (c *Client) GetHighUTXOAndSum() (*btcjson.ListUnspentResult, float64, error func CalculateTxFee(feeRateAmount btcutil.Amount, size uint64) (uint64, error) { return uint64(feeRateAmount.MulF64(float64(size) / 1024)), nil } + +func (c *Client) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) { + return c.Client.FundRawTransaction(tx, opts, isWitness) +} + +func (c *Client) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { + return c.Client.SignRawTransactionWithWallet(tx) +} diff --git a/btcclient/interface.go b/btcclient/interface.go index 12adb6a5..71b9bca5 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) @@ -37,6 +35,7 @@ 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/notifier.go b/btcclient/notifier.go index 2e31fd71..79f328b0 100644 --- a/btcclient/notifier.go +++ b/btcclient/notifier.go @@ -1,33 +1,19 @@ 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 { - RPCHost string - RPCUser string - RPCPass string - RPCCert string - RawCert string - DisableTLS bool - BlockCacheSize uint64 -} - type Bitcoind struct { RPCHost string RPCUser string @@ -48,7 +34,7 @@ func DefaultBitcoindConfig() Bitcoind { RPCHost: config.DefaultRpcBtcNodeHost, RPCUser: config.DefaultBtcNodeRpcUser, RPCPass: config.DefaultBtcNodeRpcPass, - RPCPolling: false, + RPCPolling: true, BlockPollingInterval: 30 * time.Second, TxPollingInterval: 30 * time.Second, EstimateMode: config.DefaultBtcNodeEstimateMode, @@ -59,46 +45,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 - - 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)) - } +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 &defaultBitcoindCfg } type NodeBackend struct { @@ -110,9 +67,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,104 +102,66 @@ 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, - } - } + bitcoindCfg := &chain.BitcoindConfig{ + ChainParams: params, + Host: cfg.RPCHost, + User: cfg.RPCUser, + Pass: cfg.RPCPass, + Dialer: BuildDialer(cfg.RPCHost), + PrunedModeMaxPeers: cfg.PrunedNodeMaxPeers, + } - bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg) - if err != nil { - return nil, err + if cfg.RPCPolling { + bitcoindCfg.PollingConfig = &chain.PollingConfig{ + BlockPollingInterval: cfg.BlockPollingInterval, + TxPollingInterval: cfg.TxPollingInterval, + TxPollingIntervalJitter: config.DefaultTxPollingJitter, } - - if err := bitcoindConn.Start(); err != nil { - return nil, fmt.Errorf("unable to connect to "+ - "bitcoind: %v", err) + } else { + bitcoindCfg.ZMQConfig = &chain.ZMQConfig{ + ZMQBlockHost: cfg.ZMQPubRawBlock, + ZMQTxHost: cfg.ZMQPubRawTx, + ZMQReadDeadline: cfg.ZMQReadDeadline, + MempoolPollingInterval: cfg.TxPollingInterval, + PollingIntervalJitter: config.DefaultTxPollingJitter, } + } - 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 - - } else { - certificates, err := os.ReadFile(cfg.Btcd.RPCCert) - if err != nil { - return nil, fmt.Errorf("error reading btcd cert file: %v", err) - } - certs = certificates - } - } + bitcoindConn, err := chain.NewBitcoindConn(bitcoindCfg) + if err != nil { + return nil, err + } - rpcConfig := &rpcclient.ConnConfig{ - Host: btcdHost, - Endpoint: "ws", - User: btcdUser, - Pass: btcdPass, - Certificates: certs, - DisableTLS: cfg.Btcd.DisableTLS, - DisableConnectOnNew: true, - DisableAutoReconnect: false, - } + if err := bitcoindConn.Start(); err != nil { + return nil, fmt.Errorf("unable to connect to "+ + "bitcoind: %v", err) + } - chainNotifier, err := btcdnotify.New( - rpcConfig, params, hintCache, - hintCache, blockcache.NewBlockCache(cfg.Btcd.BlockCacheSize), - ) + chainNotifier := bitcoindnotify.New( + bitcoindConn, params, hintCache, + hintCache, blockcache.NewBlockCache(cfg.BlockCacheSize), + ) - if err != nil { - return nil, err - } + return &NodeBackend{ + ChainNotifier: chainNotifier, + }, nil +} - return &NodeBackend{ - ChainNotifier: chainNotifier, - }, nil +// NewNodeBackendWithParams creates a new NodeBackend by incorporating parameter retrieval and config conversion. +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) + } - default: - return nil, fmt.Errorf("unknown node backend: %v", cfg.ActiveNodeBackend) + btcNotifier, err := NewNodeBackend(ToBitcoindConfig(cfg), btcParams, &EmptyHintCache{}) + if err != nil { + return nil, fmt.Errorf("failed to initialize notifier: %w", err) } + + return btcNotifier, nil } 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/cmd/vigilante/cmd/btcstaking_tracker.go b/cmd/vigilante/cmd/btcstaking_tracker.go index f11bf804..1874b753 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" ) @@ -61,26 +60,17 @@ 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)) } // 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 2b66c15b..fe2b7423 100644 --- a/cmd/vigilante/cmd/monitor.go +++ b/cmd/vigilante/cmd/monitor.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - bbnqccfg "github.com/babylonlabs-io/babylon/client/config" bbnqc "github.com/babylonlabs-io/babylon/client/query" "github.com/spf13/cobra" @@ -62,13 +61,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)) } @@ -80,8 +73,23 @@ func GetMonitorCmd() *cobra.Command { // register monitor metrics monitorMetrics := metrics.NewMonitorMetrics() + // create the chain notifier + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC) + if err != nil { + panic(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/cmd/vigilante/cmd/reporter.go b/cmd/vigilante/cmd/reporter.go index bbaa9317..0d763d18 100644 --- a/cmd/vigilante/cmd/reporter.go +++ b/cmd/vigilante/cmd/reporter.go @@ -2,7 +2,6 @@ package cmd import ( "fmt" - bbnclient "github.com/babylonlabs-io/babylon/client/client" "github.com/spf13/cobra" @@ -48,7 +47,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)) } @@ -62,12 +61,19 @@ func GetReporterCmd() *cobra.Command { // register reporter metrics reporterMetrics := metrics.NewReporterMetrics() + // create the chain notifier + btcNotifier, err := btcclient.NewNodeBackendWithParams(cfg.BTC) + if err != nil { + panic(err) + } + // create reporter vigilantReporter, err = reporter.New( &cfg.Reporter, rootLogger, btcClient, babylonClient, + btcNotifier, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, reporterMetrics, diff --git a/config/bitcoin.go b/config/bitcoin.go index c188dd14..0b89ea07 100644 --- a/config/bitcoin.go +++ b/config/bitcoin.go @@ -3,8 +3,6 @@ package config import ( "errors" "fmt" - "os" - "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/babylonlabs-io/vigilante/types" @@ -12,27 +10,22 @@ 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"` + Endpoint string `mapstructure:"endpoint"` + WalletPassword string `mapstructure:"wallet-password"` + WalletName string `mapstructure:"wallet-name"` + 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 +37,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 { @@ -102,22 +89,17 @@ const ( DefaultBtcNodeRpcPass = "rpcpass" DefaultBtcNodeEstimateMode = "CONSERVATIVE" DefaultBtcblockCacheSize = 20 * 1024 * 1024 // 20 MB - DefaultZmqSeqEndpoint = "tcp://127.0.0.1:29000" + DefaultZmqSeqEndpoint = "tcp://127.0.0.1:28333" DefaultZmqBlockEndpoint = "tcp://127.0.0.1:29001" DefaultZmqTxEndpoint = "tcp://127.0.0.1:29002" ) func DefaultBTCConfig() BTCConfig { return BTCConfig{ - DisableClientTLS: false, - CAFile: defaultBtcCAFile, Endpoint: DefaultRpcBtcNodeHost, - WalletEndpoint: "localhost:18554", WalletPassword: "walletpass", 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 @@ -132,35 +114,3 @@ func DefaultBTCConfig() BTCConfig { ZmqTxEndpoint: DefaultZmqTxEndpoint, } } - -func (cfg *BTCConfig) ReadCAFile() []byte { - if cfg.DisableClientTLS { - return nil - } - - // Read certificate file if TLS is not disabled. - certs, err := os.ReadFile(cfg.CAFile) - if err != nil { - // If there's an error reading the CA file, continue - // with nil certs and without the client connection. - return nil - } - - return certs -} - -func (cfg *BTCConfig) ReadWalletCAFile() []byte { - if cfg.DisableClientTLS { - // Chain server RPC TLS is disabled - return nil - } - - // Read certificate file if TLS is not disabled. - certs, err := os.ReadFile(cfg.WalletCAFile) - if err != nil { - // If there's an error reading the CA file, continue - // with nil certs and without the client connection. - return nil - } - return certs -} diff --git a/config/config.go b/config/config.go index 281c7976..f498f65a 100644 --- a/config/config.go +++ b/config/config.go @@ -18,12 +18,10 @@ const ( ) var ( - defaultBtcCAFile = filepath.Join(btcutil.AppDataDir("btcd", false), "rpc.cert") - defaultBtcWalletCAFile = filepath.Join(btcutil.AppDataDir("btcwallet", false), "rpc.cert") - defaultAppDataDir = btcutil.AppDataDir("babylon-vigilante", false) - defaultConfigFile = filepath.Join(defaultAppDataDir, defaultConfigFilename) - defaultRPCKeyFile = filepath.Join(defaultAppDataDir, "rpc.key") - defaultRPCCertFile = filepath.Join(defaultAppDataDir, "rpc.cert") + defaultAppDataDir = btcutil.AppDataDir("babylon-vigilante", false) + defaultConfigFile = filepath.Join(defaultAppDataDir, defaultConfigFilename) + defaultRPCKeyFile = filepath.Join(defaultAppDataDir, "rpc.key") + defaultRPCCertFile = filepath.Join(defaultAppDataDir, "rpc.cert") ) // Config defines the server's top level configuration diff --git a/e2etest/atomicslasher_e2e_test.go b/e2etest/atomicslasher_e2e_test.go index d30181ac..ba56d6bd 100644 --- a/e2etest/atomicslasher_e2e_test.go +++ b/e2etest/atomicslasher_e2e_test.go @@ -4,7 +4,7 @@ package e2etest import ( - "encoding/hex" + "go.uber.org/zap" "testing" "time" @@ -14,55 +14,31 @@ import ( "github.com/babylonlabs-io/vigilante/btcstaking-tracker/btcslasher" "github.com/babylonlabs-io/vigilante/config" "github.com/babylonlabs-io/vigilante/metrics" - "github.com/babylonlabs-io/vigilante/types" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) +// TestAtomicSlasher verifies the behavior of the atomic slasher by setting up delegations, +// sending slashing transactions, and ensuring that slashing is detected and executed correctly. func TestAtomicSlasher(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := map[chainhash.Hash]struct{}{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs[*hash] = struct{}{} - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) + // start WebSocket connection with Babylon for subscriber services - err = tm.BabylonClient.Start() + err := tm.BabylonClient.Start() require.NoError(t, err) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -73,9 +49,6 @@ func TestAtomicSlasher(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - metrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( @@ -84,15 +57,12 @@ func TestAtomicSlasher(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, + zap.NewNop(), metrics, ) go bsTracker.Start() defer bsTracker.Stop() - // wait for bootstrapping - time.Sleep(5 * time.Second) - bsParamsResp, err := tm.BabylonClient.BTCStakingParams() require.NoError(t, err) bsParams := bsParamsResp.Params @@ -113,23 +83,25 @@ func TestAtomicSlasher(t *testing.T) { finality provider builds slashing tx witness and sends slashing tx to Bitcoin */ victimBTCDel := btcDels[0] - victimSlashingTx, err := btcslasher.BuildSlashingTxWithWitness(victimBTCDel, &bsParams, netParams, fpSK) + victimSlashingTx, err := btcslasher.BuildSlashingTxWithWitness(victimBTCDel, &bsParams, regtestParams, fpSK) // send slashing tx to Bitcoin require.NoError(t, err) slashingTxHash, err := tm.BTCClient.SendRawTransaction(victimSlashingTx, true) require.NoError(t, err) + require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(slashingTxHash) return err == nil }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx, which will trigger atomic slasher - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash})) - // ensure slashing tx will be detected on Bitcoin require.Eventually(t, func() bool { - _, ok := submittedTxs[*slashingTxHash] - return ok + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + minedBlock := tm.mineBlock(t) + require.Equal(t, 2, len(minedBlock.Transactions)) + /* atomic slasher will detect the selective slashing on victim BTC delegation the finality provider will get slashed on Babylon @@ -149,60 +121,42 @@ func TestAtomicSlasher(t *testing.T) { slashTx2, err := bstypes.NewBTCSlashingTxFromHex(btcDel2.SlashingTxHex) require.NoError(t, err) slashingTxHash2 := slashTx2.MustGetTxHash() + require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(slashingTxHash2) t.Logf("err of getting slashingTxHash of the BTC delegation affected by atomic slashing: %v", err) return err == nil }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx, which will trigger atomic slasher - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash2})) - // ensure slashing tx 2 will be detected on Bitcoin require.Eventually(t, func() bool { - _, ok := submittedTxs[*slashingTxHash2] - return ok + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash2})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + + minedBlock = tm.mineBlock(t) + require.Equal(t, 2, len(minedBlock.Transactions)) } +// TestAtomicSlasher_Unbonding tests the atomic slasher's handling of unbonding BTC delegations, +// including the creation and detection of unbonding slashing transactions. func TestAtomicSlasher_Unbonding(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := map[chainhash.Hash]struct{}{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs[*hash] = struct{}{} - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) + // start WebSocket connection with Babylon for subscriber services - err = tm.BabylonClient.Start() + err := tm.BabylonClient.Start() require.NoError(t, err) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -213,10 +167,8 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -224,15 +176,12 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) go bsTracker.Start() defer bsTracker.Stop() - // wait for bootstrapping - time.Sleep(5 * time.Second) - bsParamsResp, err := tm.BabylonClient.BTCStakingParams() require.NoError(t, err) bsParams := bsParamsResp.Params @@ -252,6 +201,7 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { btcDelsResp2, err := tm.BabylonClient.BTCDelegations(bstypes.BTCDelegationStatus_ACTIVE, nil) require.NoError(t, err) require.Len(t, btcDelsResp2.BtcDelegations, 2) + // NOTE: `BTCDelegations` API does not return BTC delegations in created time order // thus we need to find out the 2nd BTC delegation one-by-one var btcDel2 *bstypes.BTCDelegationResponse @@ -262,6 +212,8 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { } } + require.NotNil(t, btcDel2, "err second delegation not found") + /* the victim BTC delegation unbonds */ @@ -270,8 +222,9 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { /* finality provider builds unbonding slashing tx witness and sends it to Bitcoin */ - victimUnbondingSlashingTx, err := btcslasher.BuildUnbondingSlashingTxWithWitness(victimBTCDel, &bsParams, netParams, fpSK) + victimUnbondingSlashingTx, err := btcslasher.BuildUnbondingSlashingTxWithWitness(victimBTCDel, &bsParams, regtestParams, fpSK) require.NoError(t, err) + // send slashing tx to Bitcoin // NOTE: sometimes unbonding slashing tx is not immediately spendable for some reason var unbondingSlashingTxHash *chainhash.Hash @@ -283,6 +236,7 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { } return true }, eventuallyWaitTimeOut, eventuallyPollTime) + // unbonding slashing tx is eventually queryable require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(unbondingSlashingTxHash) @@ -293,13 +247,13 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { return true }, eventuallyWaitTimeOut, eventuallyPollTime) // mine a block that includes unbonding slashing tx, which will trigger atomic slasher - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingSlashingTxHash})) - // ensure unbonding slashing tx will be detected on Bitcoin require.Eventually(t, func() bool { - _, ok := submittedTxs[*unbondingSlashingTxHash] - return ok + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingSlashingTxHash})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + minedBlock := tm.mineBlock(t) + require.Equal(t, 2, len(minedBlock.Transactions)) + /* atomic slasher will detect the selective slashing on victim BTC delegation the finality provider will get slashed on Babylon @@ -317,17 +271,19 @@ func TestAtomicSlasher_Unbonding(t *testing.T) { */ slashingTx2, err := bstypes.NewBTCSlashingTxFromHex(btcDel2.SlashingTxHex) require.NoError(t, err) + slashingTxHash2 := slashingTx2.MustGetTxHash() require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(slashingTxHash2) t.Logf("err of getting slashingTxHash of the BTC delegation affected by atomic slashing: %v", err) return err == nil }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx, which will trigger atomic slasher - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash2})) - // ensure slashing tx 2 will be detected on Bitcoin require.Eventually(t, func() bool { - _, ok := submittedTxs[*slashingTxHash2] - return ok + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingTxHash2})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + + minedBlock = tm.mineBlock(t) + require.Equal(t, 2, len(minedBlock.Transactions)) } diff --git a/e2etest/babylon_node_handler.go b/e2etest/babylon_node_handler.go index 0590047a..e77f623a 100644 --- a/e2etest/babylon_node_handler.go +++ b/e2etest/babylon_node_handler.go @@ -22,7 +22,7 @@ var ( func baseDirBabylondir() (string, error) { tempPath := os.TempDir() - tempName, err := os.MkdirTemp(tempPath, "zBabylonTestVigilante") + tempName, err := os.MkdirTemp(tempPath, "BabylonTestVigilante") if err != nil { return "", err } @@ -123,7 +123,7 @@ type BabylonNodeHandler struct { babylonNode *babylonNode } -func NewBabylonNodeHandler() (*BabylonNodeHandler, error) { +func NewBabylonNodeHandler(baseHeaderHex string, slashingAddress string) (*BabylonNodeHandler, error) { testDir, err := baseDirBabylondir() if err != nil { return nil, err @@ -140,6 +140,9 @@ func NewBabylonNodeHandler() (*BabylonNodeHandler, error) { "--btc-finalization-timeout=4", "--btc-confirmation-depth=2", "--additional-sender-account", + "--btc-network=regtest", + fmt.Sprintf("--slashing-address=%s", slashingAddress), + fmt.Sprintf("--btc-base-header=%s", baseHeaderHex), "--covenant-quorum=1", fmt.Sprintf("--covenant-pks=%s", bbn.NewBIP340PubKeyFromBTCPK(juryPK).MarshalHex()), ) diff --git a/e2etest/bitcoind_node_setup.go b/e2etest/bitcoind_node_setup.go new file mode 100644 index 00000000..e6686eaf --- /dev/null +++ b/e2etest/bitcoind_node_setup.go @@ -0,0 +1,117 @@ +package e2etest + +import ( + "encoding/json" + "fmt" + "github.com/babylonlabs-io/vigilante/e2etest/container" + "github.com/stretchr/testify/require" + "os" + "strconv" + "strings" + "testing" + "time" +) + +var ( + startTimeout = 30 * time.Second +) + +type CreateWalletResponse struct { + Name string `json:"name"` + Warning string `json:"warning"` +} + +type GenerateBlockResponse struct { + // address of the recipient of rewards + Address string `json:"address"` + // blocks generated + Blocks []string `json:"blocks"` +} + +type BitcoindTestHandler struct { + t *testing.T + m *container.Manager +} + +func NewBitcoindHandler(t *testing.T) *BitcoindTestHandler { + manager, err := container.NewManager() + require.NoError(t, err) + + return &BitcoindTestHandler{ + t: t, + m: manager, + } +} + +func (h *BitcoindTestHandler) Start() { + tempPath, err := os.MkdirTemp("", "vigilante-test-*") + require.NoError(h.t, err) + + h.t.Cleanup(func() { + _ = os.RemoveAll(tempPath) + }) + + _, err = h.m.RunBitcoindResource(tempPath) + require.NoError(h.t, err) + + h.t.Cleanup(func() { + _ = h.m.ClearResources() + }) + + require.Eventually(h.t, func() bool { + _, err := h.GetBlockCount() + if err != nil { + h.t.Logf("failed to get block count: %v", err) + } + return err == nil + }, startTimeout, 500*time.Millisecond, "bitcoind did not start") +} + +// GetBlockCount retrieves the current number of blocks in the blockchain from the Bitcoind. +func (h *BitcoindTestHandler) GetBlockCount() (int, error) { + buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"getblockcount"}) + if err != nil { + return 0, err + } + + parsedBuffStr := strings.TrimSuffix(buff.String(), "\n") + + return strconv.Atoi(parsedBuffStr) +} + +// GenerateBlocks mines a specified number of blocks in the Bitcoind. +func (h *BitcoindTestHandler) GenerateBlocks(count int) *GenerateBlockResponse { + buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"-generate", fmt.Sprintf("%d", count)}) + require.NoError(h.t, err) + + var response GenerateBlockResponse + err = json.Unmarshal(buff.Bytes(), &response) + require.NoError(h.t, err) + + return &response +} + +// CreateWallet creates a new wallet with the specified name and passphrase in the Bitcoind +func (h *BitcoindTestHandler) CreateWallet(walletName string, passphrase string) *CreateWalletResponse { + // last arg is true which indicates we are using descriptor wallets they do not allow dumping private keys. + buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"createwallet", walletName, "false", "false", passphrase, "false", "true"}) + require.NoError(h.t, err) + + var response CreateWalletResponse + err = json.Unmarshal(buff.Bytes(), &response) + require.NoError(h.t, err) + + return &response +} + +// InvalidateBlock invalidates blocks starting from specified block hash +func (h *BitcoindTestHandler) InvalidateBlock(blockHash string) { + _, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"invalidateblock", blockHash}) + require.NoError(h.t, err) +} + +// ImportDescriptors imports a given Bitcoin address descriptor into the Bitcoind +func (h *BitcoindTestHandler) ImportDescriptors(descriptor string) { + _, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"importdescriptors", descriptor}) + require.NoError(h.t, err) +} diff --git a/e2etest/btcd_wallet_handler.go b/e2etest/btcd_wallet_handler.go deleted file mode 100644 index 52a97813..00000000 --- a/e2etest/btcd_wallet_handler.go +++ /dev/null @@ -1,179 +0,0 @@ -package e2etest - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" -) - -func baseDir() (string, error) { - dirPath := filepath.Join(os.TempDir(), "btcdwallet", "rpctest") - err := os.MkdirAll(dirPath, 0755) - return dirPath, err -} - -type wallet struct { - cmd *exec.Cmd - pidFile string - dataDir string -} - -func newWallet(dataDir string, cmd *exec.Cmd) *wallet { - return &wallet{ - dataDir: dataDir, - cmd: cmd, - } -} - -func (n *wallet) start() error { - if err := n.cmd.Start(); err != nil { - return err - } - - pid, err := os.Create(filepath.Join(n.dataDir, - fmt.Sprintf("%s.pid", "config"))) - if err != nil { - return err - } - - n.pidFile = pid.Name() - if _, err = fmt.Fprintf(pid, "%d\n", n.cmd.Process.Pid); err != nil { - return err - } - - if err := pid.Close(); err != nil { - return err - } - - return nil -} - -func (n *wallet) stop() (err error) { - if n.cmd == nil || n.cmd.Process == nil { - // return if not properly initialized - // or error starting the process - return nil - } - - defer func() { - err = n.cmd.Wait() - }() - - if runtime.GOOS == "windows" { - return n.cmd.Process.Signal(os.Kill) - } - return n.cmd.Process.Signal(os.Interrupt) -} - -func (n *wallet) cleanup() error { - if n.pidFile != "" { - if err := os.Remove(n.pidFile); err != nil { - log.Errorf("unable to remove file %s: %v", n.pidFile, - err) - } - } - - dirs := []string{ - n.dataDir, - } - var err error - for _, dir := range dirs { - if err = os.RemoveAll(dir); err != nil { - log.Errorf("Cannot remove dir %s: %v", dir, err) - } - } - return err -} - -func (n *wallet) shutdown() error { - if err := n.stop(); err != nil { - return err - } - if err := n.cleanup(); err != nil { - return err - } - return nil -} - -type WalletHandler struct { - wallet *wallet -} - -func NewWalletHandler(btcdCert []byte, walletPath string, btcdHost string) (*WalletHandler, error) { - testDir, err := baseDir() - logsPath := filepath.Join(testDir, "logs") - walletDir := filepath.Join(testDir, "simnet") - - if err := os.Mkdir(walletDir, os.ModePerm); err != nil { - return nil, err - } - - if err != nil { - return nil, err - } - - certFile := filepath.Join(testDir, "rpc.cert") - - dir := fmt.Sprintf("--appdata=%s", testDir) - logDir := fmt.Sprintf("--logdir=%s", logsPath) - certDir := fmt.Sprintf("--cafile=%s", certFile) - hostConf := fmt.Sprintf("--rpcconnect=%s", btcdHost) - - // Write cert and key files. - if err = os.WriteFile(certFile, btcdCert, 0666); err != nil { - return nil, err - } - - tempWalletPath := filepath.Join(walletDir, "wallet.db") - - // Read all content of src to data, may cause OOM for a large file. - data, err := os.ReadFile(walletPath) - - if err != nil { - return nil, err - } - - // Write data to dst - err = os.WriteFile(tempWalletPath, data, 0644) - - if err != nil { - return nil, err - } - - // btcwallet --btcdusername=user --btcdpassword=pass --username=user --password=pass --noinitialload --noservertls --simnet - createCmd := exec.Command( - "btcwallet", - "--debuglevel=debug", - "--btcdusername=user", - "--btcdpassword=pass", - "--username=user", - "--password=pass", - "--noservertls", - "--simnet", - hostConf, - certDir, - dir, - logDir, - ) - - return &WalletHandler{ - wallet: newWallet(testDir, createCmd), - }, nil -} - -func (w *WalletHandler) Start() error { - if err := w.wallet.start(); err != nil { - return w.wallet.cleanup() - } - return nil -} - -func (w *WalletHandler) Stop() error { - if err := w.wallet.shutdown(); err != nil { - return err - } - - return nil -} diff --git a/e2etest/container/config.go b/e2etest/container/config.go new file mode 100644 index 00000000..01b75746 --- /dev/null +++ b/e2etest/container/config.go @@ -0,0 +1,23 @@ +package container + +// ImageConfig contains all images and their respective tags +// needed for running e2e tests. +type ImageConfig struct { + BitcoindRepository string + BitcoindVersion string +} + +//nolint:deadcode +const ( + dockerBitcoindRepository = "lncm/bitcoind" + dockerBitcoindVersionTag = "v27.0" +) + +// NewImageConfig returns ImageConfig needed for running e2e test. +func NewImageConfig() ImageConfig { + config := ImageConfig{ + BitcoindRepository: dockerBitcoindRepository, + BitcoindVersion: dockerBitcoindVersionTag, + } + return config +} diff --git a/e2etest/container/container.go b/e2etest/container/container.go new file mode 100644 index 00000000..a2bab703 --- /dev/null +++ b/e2etest/container/container.go @@ -0,0 +1,189 @@ +package container + +import ( + "bytes" + "context" + "fmt" + "regexp" + "testing" + "time" + + "github.com/ory/dockertest/v3" + "github.com/ory/dockertest/v3/docker" + "github.com/stretchr/testify/require" +) + +const ( + bitcoindContainerName = "bitcoind-test" +) + +var errRegex = regexp.MustCompile(`(E|e)rror`) + +// Manager is a wrapper around all Docker instances, and the Docker API. +// It provides utilities to run and interact with all Docker containers used within e2e testing. +type Manager struct { + cfg ImageConfig + pool *dockertest.Pool + resources map[string]*dockertest.Resource +} + +// NewManager creates a new Manager instance and initializes +// all Docker specific utilities. Returns an error if initialization fails. +func NewManager() (docker *Manager, err error) { + docker = &Manager{ + cfg: NewImageConfig(), + resources: make(map[string]*dockertest.Resource), + } + docker.pool, err = dockertest.NewPool("") + if err != nil { + return nil, err + } + return docker, nil +} + +func (m *Manager) ExecBitcoindCliCmd(t *testing.T, command []string) (bytes.Buffer, bytes.Buffer, error) { + // this is currently hardcoded, as it will be the same for all tests + cmd := []string{"bitcoin-cli", "-chain=regtest", "-rpcuser=user", "-rpcpassword=pass"} + cmd = append(cmd, command...) + return m.ExecCmd(t, bitcoindContainerName, cmd) +} + +// ExecCmd executes command by running it on the given container. +// It word for word `error` in output to discern between error and regular output. +// It retures stdout and stderr as bytes.Buffer and an error if the command fails. +func (m *Manager) ExecCmd(t *testing.T, containerName string, command []string) (bytes.Buffer, bytes.Buffer, error) { + if _, ok := m.resources[containerName]; !ok { + return bytes.Buffer{}, bytes.Buffer{}, fmt.Errorf("no resource %s found", containerName) + } + containerId := m.resources[containerName].Container.ID + + var ( + outBuf bytes.Buffer + errBuf bytes.Buffer + ) + + timeout := 20 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + t.Logf("\n\nRunning: \"%s\"", command) + + // We use the `require.Eventually` function because it is only allowed to do one transaction per block without + // sequence numbers. For simplicity, we avoid keeping track of the sequence number and just use the `require.Eventually`. + require.Eventually( + t, + func() bool { + exec, err := m.pool.Client.CreateExec(docker.CreateExecOptions{ + Context: ctx, + AttachStdout: true, + AttachStderr: true, + Container: containerId, + User: "root", + Cmd: command, + }) + + if err != nil { + t.Logf("failed to create exec: %v", err) + return false + } + + err = m.pool.Client.StartExec(exec.ID, docker.StartExecOptions{ + Context: ctx, + Detach: false, + OutputStream: &outBuf, + ErrorStream: &errBuf, + }) + if err != nil { + t.Logf("failed to start exec: %v", err) + return false + } + + errBufString := errBuf.String() + // Note that this does not match all errors. + // This only works if CLI outputs "Error" or "error" + // to stderr. + if errRegex.MatchString(errBufString) { + t.Log("\nstderr:") + t.Log(errBufString) + + t.Log("\nstdout:") + t.Log(outBuf.String()) + return false + } + + return true + }, + timeout, + 500*time.Millisecond, + "command failed", + ) + + return outBuf, errBuf, nil +} + +func (m *Manager) RunBitcoindResource( + bitcoindCfgPath string, +) (*dockertest.Resource, error) { + bitcoindResource, err := m.pool.RunWithOptions( + &dockertest.RunOptions{ + Name: bitcoindContainerName, + Repository: m.cfg.BitcoindRepository, + Tag: m.cfg.BitcoindVersion, + User: "root:root", + Mounts: []string{ + fmt.Sprintf("%s/:/data/.bitcoin", bitcoindCfgPath), + }, + ExposedPorts: []string{ + "8332", + "8333", + "28332", + "28333", + "18443", + "18444", + }, + PortBindings: map[docker.Port][]docker.PortBinding{ + "8332/tcp": {{HostIP: "", HostPort: "8332"}}, + "8333/tcp": {{HostIP: "", HostPort: "8333"}}, + "28332/tcp": {{HostIP: "", HostPort: "28332"}}, + "28333/tcp": {{HostIP: "", HostPort: "28333"}}, + "18443/tcp": {{HostIP: "", HostPort: "18443"}}, + "18444/tcp": {{HostIP: "", HostPort: "18444"}}, + }, + Cmd: []string{ + "-debug=1", + "-regtest", + "-txindex", + "-rpcuser=user", + "-rpcpassword=pass", + "-rpcallowip=0.0.0.0/0", + "-rpcbind=0.0.0.0", + "-zmqpubsequence=tcp://0.0.0.0:28333", + "-fallbackfee=0.0002", + }, + }, + noRestart, + ) + if err != nil { + return nil, err + } + m.resources[bitcoindContainerName] = bitcoindResource + return bitcoindResource, nil +} + +// ClearResources removes all outstanding Docker resources created by the Manager. +func (m *Manager) ClearResources() error { + for _, resource := range m.resources { + if err := m.pool.Purge(resource); err != nil { + return err + } + } + + return nil +} + +func noRestart(config *docker.HostConfig) { + // in this case we don't want the nodes to restart on failure + config.RestartPolicy = docker.RestartPolicy{ + Name: "no", + } +} diff --git a/e2etest/reporter_e2e_test.go b/e2etest/reporter_e2e_test.go index 142fe3a6..2d25fa35 100644 --- a/e2etest/reporter_e2e_test.go +++ b/e2etest/reporter_e2e_test.go @@ -4,18 +4,14 @@ package e2etest import ( - "math/rand" + "github.com/babylonlabs-io/vigilante/btcclient" + "github.com/babylonlabs-io/vigilante/netparams" + "sync" "testing" "time" - "github.com/babylonlabs-io/babylon/testutil/datagen" "github.com/babylonlabs-io/vigilante/metrics" "github.com/babylonlabs-io/vigilante/reporter" - "github.com/babylonlabs-io/vigilante/types" - "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/integration/rpctest" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) @@ -24,95 +20,61 @@ var ( ) func (tm *TestManager) BabylonBTCChainMatchesBtc(t *testing.T) bool { - tipHash, tipHeight, err := tm.BTCClient.GetBestBlock() + tipHeight, err := tm.TestRpcClient.GetBlockCount() + require.NoError(t, err) + tipHash, err := tm.TestRpcClient.GetBlockHash(tipHeight) require.NoError(t, err) bbnBtcLcTip, err := tm.BabylonClient.BTCHeaderChainTip() require.NoError(t, err) - return uint64(tipHeight) == bbnBtcLcTip.Header.Height && tipHash.String() == bbnBtcLcTip.Header.HashHex -} -func (tm *TestManager) GenerateAndSubmitsNBlocksFromTip(N int) { - var ut time.Time - - for i := 0; i < N; i++ { - tm.MinerNode.GenerateAndSubmitBlock(nil, -1, ut) - } + return uint64(tipHeight) == bbnBtcLcTip.Header.Height && tipHash.String() == bbnBtcLcTip.Header.HashHex } func (tm *TestManager) GenerateAndSubmitBlockNBlockStartingFromDepth(t *testing.T, N int, depth uint32) { - r := rand.New(rand.NewSource(time.Now().Unix())) - if depth == 0 { // depth 0 means we are starting from tip - tm.GenerateAndSubmitsNBlocksFromTip(N) + tm.BitcoindHandler.GenerateBlocks(N) return } - _, bestHeight, err := tm.MinerNode.Client.GetBestBlock() + height, err := tm.TestRpcClient.GetBlockCount() require.NoError(t, err) - startingBlockHeight := bestHeight - int32(depth) + startingBlockHeight := height - int64(depth) - blockHash, err := tm.MinerNode.Client.GetBlockHash(int64(startingBlockHeight)) + blockHash, err := tm.TestRpcClient.GetBlockHash(startingBlockHeight) require.NoError(t, err) - startingBlockMsg, err := tm.MinerNode.Client.GetBlock(blockHash) - require.NoError(t, err) - - startingBlock := btcutil.NewBlock(startingBlockMsg) - startingBlock.SetHeight(startingBlockHeight) - - arr := datagen.GenRandomByteArray(r, 20) - add, err := btcutil.NewAddressScriptHashFromHash(arr, tm.MinerNode.ActiveNet) - require.NoError(t, err) - - var lastSubmittedBlock *btcutil.Block - var ut time.Time + // invalidate blocks from this height + tm.BitcoindHandler.InvalidateBlock(blockHash.String()) for i := 0; i < N; i++ { - var blockToSubmit *btcutil.Block - - if lastSubmittedBlock == nil { - // first block to submit start from starting block - newBlock, err := rpctest.CreateBlock(startingBlock, nil, rpctest.BlockVersion, - ut, add, nil, tm.MinerNode.ActiveNet) - require.NoError(t, err) - blockToSubmit = newBlock - } else { - newBlock, err := rpctest.CreateBlock(lastSubmittedBlock, nil, rpctest.BlockVersion, - ut, add, nil, tm.MinerNode.ActiveNet) - require.NoError(t, err) - blockToSubmit = newBlock - } - err = tm.MinerNode.Client.SubmitBlock(blockToSubmit, nil) - require.NoError(t, err) - lastSubmittedBlock = blockToSubmit + tm.BitcoindHandler.GenerateBlocks(N) } } func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) { // no need to much mature outputs, we are not going to submit transactions in this test - numMatureOutputs := uint32(2) - - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - } + numMatureOutputs := uint32(150) - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) reporterMetrics := metrics.NewReporterMetrics() + + // create the chain notifier + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.ToBitcoindConfig(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, @@ -120,15 +82,26 @@ func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) { require.NoError(t, err) // start a routine that mines BTC blocks very fast + var wg sync.WaitGroup + stopChan := make(chan struct{}) + + wg.Add(1) go func() { + defer wg.Done() ticker := time.NewTicker(10 * time.Second) - for range ticker.C { - tm.GenerateAndSubmitsNBlocksFromTip(1) + defer ticker.Stop() + for { + select { + case <-ticker.C: + tm.BitcoindHandler.GenerateBlocks(1) + case <-stopChan: + return + } } }() // mine some BTC headers - tm.GenerateAndSubmitsNBlocksFromTip(2) + tm.BitcoindHandler.GenerateBlocks(1) // start reporter vigilantReporter.Start() @@ -138,38 +111,39 @@ func TestReporter_BoostrapUnderFrequentBTCHeaders(t *testing.T) { require.Eventually(t, func() bool { return tm.BabylonBTCChainMatchesBtc(t) }, longEventuallyWaitTimeOut, eventuallyPollTime) + + close(stopChan) + wg.Wait() } func TestRelayHeadersAndHandleRollbacks(t *testing.T) { // no need to much mature outputs, we are not going to submit transactions in this test - numMatureOutputs := uint32(2) - - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - } + numMatureOutputs := uint32(150) - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) + tm := StartManager(t, numMatureOutputs) // this is necessary to receive notifications about new transactions entering mempool defer tm.Stop(t) reporterMetrics := metrics.NewReporterMetrics() + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.ToBitcoindConfig(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, ) require.NoError(t, err) + vigilantReporter.Start() defer vigilantReporter.Stop() @@ -178,7 +152,7 @@ func TestRelayHeadersAndHandleRollbacks(t *testing.T) { }, longEventuallyWaitTimeOut, eventuallyPollTime) // generate 3, we are submitting headers 1 by 1 so we use small amount as this is slow process - tm.GenerateAndSubmitsNBlocksFromTip(3) + tm.BitcoindHandler.GenerateBlocks(3) require.Eventually(t, func() bool { return tm.BabylonBTCChainMatchesBtc(t) @@ -195,29 +169,26 @@ func TestRelayHeadersAndHandleRollbacks(t *testing.T) { func TestHandleReorgAfterRestart(t *testing.T) { // no need to much mature outputs, we are not going to submit transactions in this test - numMatureOutputs := uint32(2) - - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - } + numMatureOutputs := uint32(150) - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) + tm := StartManager(t, numMatureOutputs) // this is necessary to receive notifications about new transactions entering mempool defer tm.Stop(t) reporterMetrics := metrics.NewReporterMetrics() + btcParams, err := netparams.GetBTCParams(tm.Config.BTC.NetParams) + require.NoError(t, err) + btcCfg := btcclient.ToBitcoindConfig(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, @@ -243,12 +214,16 @@ func TestHandleReorgAfterRestart(t *testing.T) { // // we will start from block before tip and submit 2 new block this should trigger rollback tm.GenerateAndSubmitBlockNBlockStartingFromDepth(t, 2, 1) + btcClient := initBTCClientWithSubscriber(t, tm.Config) //current tm.BtcClient already has an active zmq subscription, would panic + defer btcClient.Stop() + // Start new reporter vigilantReporterNew, err := reporter.New( &tm.Config.Reporter, logger, - tm.BTCClient, + btcClient, tm.BabylonClient, + btcNotifier, tm.Config.Common.RetrySleepTime, tm.Config.Common.MaxRetrySleepTime, reporterMetrics, @@ -261,5 +236,4 @@ func TestHandleReorgAfterRestart(t *testing.T) { require.Eventually(t, func() bool { return tm.BabylonBTCChainMatchesBtc(t) }, longEventuallyWaitTimeOut, eventuallyPollTime) - } diff --git a/e2etest/slasher_e2e_test.go b/e2etest/slasher_e2e_test.go index c83ae3e4..01a87b5c 100644 --- a/e2etest/slasher_e2e_test.go +++ b/e2etest/slasher_e2e_test.go @@ -4,7 +4,7 @@ package e2etest import ( - "encoding/hex" + "go.uber.org/zap" "testing" "time" @@ -14,50 +14,23 @@ 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/types" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) func TestSlasher_GracefulShutdown(t *testing.T) { - numMatureOutputs := uint32(5) - - submittedTxs := []*chainhash.Hash{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs = append(submittedTxs, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + numMatureOutputs := uint32(300) + + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -68,10 +41,8 @@ func TestSlasher_GracefulShutdown(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -79,8 +50,8 @@ func TestSlasher_GracefulShutdown(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) go bsTracker.Start() @@ -88,6 +59,7 @@ func TestSlasher_GracefulShutdown(t *testing.T) { // wait for bootstrapping time.Sleep(10 * time.Second) + tm.BTCClient.Stop() // gracefully shut down defer bsTracker.Stop() } @@ -96,42 +68,19 @@ func TestSlasher_Slasher(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := []*chainhash.Hash{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs = append(submittedTxs, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) // start WebSocket connection with Babylon for subscriber services - err = tm.BabylonClient.Start() + err := tm.BabylonClient.Start() require.NoError(t, err) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -142,10 +91,7 @@ func TestSlasher_Slasher(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -153,8 +99,8 @@ func TestSlasher_Slasher(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) go bsTracker.Start() defer bsTracker.Stop() @@ -175,60 +121,34 @@ func TestSlasher_Slasher(t *testing.T) { require.NoError(t, err) slashingMsgTxHash1 := slashingMsgTx.TxHash() slashingMsgTxHash := &slashingMsgTxHash1 - // slashing tx will eventually enter mempool - require.Eventually(t, func() bool { - _, err := tm.BTCClient.GetRawTransaction(slashingMsgTxHash) - t.Logf("err of getting slashingMsgTxHash: %v", err) - return err == nil - }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingMsgTxHash})) - // ensure 2 txs will eventually be received (staking tx and slashing tx) require.Eventually(t, func() bool { - return len(submittedTxs) == 2 + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingMsgTxHash})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + + minedBlock := tm.mineBlock(t) + // ensure 2 txs will eventually be received (staking tx and slashing tx) + require.Equal(t, 2, len(minedBlock.Transactions)) } func TestSlasher_SlashingUnbonding(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := []*chainhash.Hash{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs = append(submittedTxs, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) // start WebSocket connection with Babylon for subscriber services - err = tm.BabylonClient.Start() + err := tm.BabylonClient.Start() require.NoError(t, err) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -239,10 +159,7 @@ func TestSlasher_SlashingUnbonding(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -250,8 +167,8 @@ func TestSlasher_SlashingUnbonding(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) go bsTracker.Start() defer bsTracker.Stop() @@ -283,8 +200,14 @@ func TestSlasher_SlashingUnbonding(t *testing.T) { _, err := tm.BTCClient.GetRawTransaction(unbondingSlashingMsgTxHash) return err == nil }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingSlashingMsgTxHash})) + require.Eventually(t, func() bool { + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingSlashingMsgTxHash})) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + tm.mineBlock(t) + // ensure tx is eventually on Bitcoin require.Eventually(t, func() bool { res, err := tm.BTCClient.GetRawTransactionVerbose(unbondingSlashingMsgTxHash) @@ -299,32 +222,10 @@ func TestSlasher_Bootstrapping(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := []*chainhash.Hash{} - blockEventChan := make(chan *types.BlockEvent, 1000) - - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs = append(submittedTxs, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) // start WebSocket connection with Babylon for subscriber services - err = tm.BabylonClient.Start() + err := tm.BabylonClient.Start() require.NoError(t, err) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) @@ -338,11 +239,10 @@ func TestSlasher_Bootstrapping(t *testing.T) { tm.VoteAndEquivocate(t, fpSK) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -353,10 +253,7 @@ func TestSlasher_Bootstrapping(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -364,8 +261,8 @@ func TestSlasher_Bootstrapping(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) // bootstrap BTC staking tracker @@ -377,15 +274,13 @@ func TestSlasher_Bootstrapping(t *testing.T) { require.NoError(t, err) slashingMsgTxHash1 := slashingMsgTx.TxHash() slashingMsgTxHash := &slashingMsgTxHash1 - require.Eventually(t, func() bool { - _, err := tm.BTCClient.GetRawTransaction(slashingMsgTxHash) - t.Logf("err of getting slashingMsgTxHash: %v", err) - return err == nil - }, eventuallyWaitTimeOut, eventuallyPollTime) + // mine a block that includes slashing tx - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingMsgTxHash})) - // ensure 2 txs will eventually be received (staking tx and slashing tx) require.Eventually(t, func() bool { - return len(submittedTxs) == 2 + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{slashingMsgTxHash})) == 1 }, eventuallyWaitTimeOut, eventuallyPollTime) + + minedBlock := tm.mineBlock(t) + // ensure 2 txs will eventually be received (staking tx and slashing tx) + require.Equal(t, 2, len(minedBlock.Transactions)) } diff --git a/e2etest/submitter_e2e_test.go b/e2etest/submitter_e2e_test.go index b4c1383a..55284a8f 100644 --- a/e2etest/submitter_e2e_test.go +++ b/e2etest/submitter_e2e_test.go @@ -13,7 +13,6 @@ import ( checkpointingtypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" @@ -24,22 +23,9 @@ import ( func TestSubmitterSubmission(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - numMatureOutputs := uint32(5) - - var submittedTransactions []*chainhash.Hash - - // We are setting handler for transaction hitting the mempool, to be sure we will - // pass transaction to the miner, in the same order as they were submitted by submitter - handlers := &rpcclient.NotificationHandlers{ - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTransactions = append(submittedTransactions, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, nil) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) + numMatureOutputs := uint32(300) + + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) randomCheckpoint := datagen.GenRandomRawCheckpointWithMeta(r) @@ -66,11 +52,12 @@ func TestSubmitterSubmission(t *testing.T) { }, nil).AnyTimes() tm.Config.Submitter.PollingIntervalSeconds = 2 + // create submitter vigilantSubmitter, _ := submitter.New( &tm.Config.Submitter, logger, - tm.BTCWalletClient, + tm.BTCClient, mockBabylonClient, subAddr, tm.Config.Common.RetrySleepTime, @@ -85,40 +72,32 @@ func TestSubmitterSubmission(t *testing.T) { vigilantSubmitter.WaitForShutdown() }() - // wait for our 2 op_returns with epoch 1 checkpoint to hit the mempool and then - // retrieve them from there - // - // TODO: to assert that those are really transactions send by submitter, we would - // need to expose sentCheckpointInfo from submitter + // wait for our 2 op_returns with epoch 1 checkpoint to hit the mempool + var mempoolTxs []*chainhash.Hash + require.Eventually(t, func() bool { + var err error + mempoolTxs, err = tm.BTCClient.GetRawMempool() + require.NoError(t, err) + return len(mempoolTxs) > 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + require.NotNil(t, mempoolTxs) + require.Eventually(t, func() bool { - return len(submittedTransactions) == 2 + return len(tm.RetrieveTransactionFromMempool(t, mempoolTxs)) == 2 }, eventuallyWaitTimeOut, eventuallyPollTime) - sendTransactions := tm.RetrieveTransactionFromMempool(t, submittedTransactions) // mine a block with those transactions - blockWithOpReturnTranssactions := tm.MineBlockWithTxs(t, sendTransactions) + blockWithOpReturnTransactions := tm.mineBlock(t) // block should have 3 transactions, 2 from submitter and 1 coinbase - require.Equal(t, len(blockWithOpReturnTranssactions.Transactions), 3) + require.Equal(t, len(blockWithOpReturnTransactions.Transactions), 3) } func TestSubmitterSubmissionReplace(t *testing.T) { r := rand.New(rand.NewSource(time.Now().Unix())) - numMatureOutputs := uint32(5) - - var submittedTransactions []*chainhash.Hash - - // We are setting handler for transaction hitting the mempool, to be sure we will - // pass transaction to the miner, in the same order as they were submitted by submitter - handlers := &rpcclient.NotificationHandlers{ - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTransactions = append(submittedTransactions, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, nil) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) + numMatureOutputs := uint32(300) + + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) randomCheckpoint := datagen.GenRandomRawCheckpointWithMeta(r) @@ -151,7 +130,7 @@ func TestSubmitterSubmissionReplace(t *testing.T) { vigilantSubmitter, _ := submitter.New( &tm.Config.Submitter, logger, - tm.BTCWalletClient, + tm.BTCClient, mockBabylonClient, subAddr, tm.Config.Common.RetrySleepTime, @@ -168,27 +147,27 @@ func TestSubmitterSubmissionReplace(t *testing.T) { // wait for our 2 op_returns with epoch 1 checkpoint to hit the mempool and then // retrieve them from there - // - // TODO: to assert that those are really transactions send by submitter, we would - // need to expose sentCheckpointInfo from submitter - require.Eventually(t, func() bool { - return len(submittedTransactions) == 2 - }, eventuallyWaitTimeOut, eventuallyPollTime) - - sendTransactions := tm.RetrieveTransactionFromMempool(t, submittedTransactions) - - // at this point our submitter already sent 2 checkpoint transactions which landed in mempool. - // Zero out submittedTransactions, and wait for a new tx2 to be submitted and accepted - // it should be replacements for the previous one. - submittedTransactions = []*chainhash.Hash{} + txsMap := make(map[string]struct{}) + var sendTransactions []*btcutil.Tx + var mempoolTxs []*chainhash.Hash require.Eventually(t, func() bool { - // we only replace tx2 of the checkpoint, thus waiting for 1 tx to arrive - return len(submittedTransactions) == 1 - }, eventuallyWaitTimeOut, eventuallyPollTime) - - transactionReplacement := tm.RetrieveTransactionFromMempool(t, submittedTransactions) - resendTx2 := transactionReplacement[0] + var err error + mempoolTxs, err = tm.BTCClient.GetRawMempool() + require.NoError(t, err) + for _, hash := range mempoolTxs { + hashStr := hash.String() + if _, exists := txsMap[hashStr]; !exists { + tx, err := tm.BTCClient.GetRawTransaction(hash) + require.NoError(t, err) + txsMap[hashStr] = struct{}{} + sendTransactions = append(sendTransactions, tx) + } + } + return len(txsMap) == 3 + }, eventuallyWaitTimeOut, 50*time.Millisecond) + + resendTx2 := sendTransactions[2] // Here check that sendTransactions1 are replacements for sendTransactions, i.e they should have: // 1. same @@ -196,11 +175,10 @@ func TestSubmitterSubmissionReplace(t *testing.T) { // 3. different signatures require.Equal(t, sendTransactions[1].MsgTx().TxIn[0].PreviousOutPoint, resendTx2.MsgTx().TxIn[0].PreviousOutPoint) require.Less(t, resendTx2.MsgTx().TxOut[1].Value, sendTransactions[1].MsgTx().TxOut[1].Value) - require.NotEqual(t, sendTransactions[1].MsgTx().TxIn[0].SignatureScript, resendTx2.MsgTx().TxIn[0].SignatureScript) + require.NotEqual(t, sendTransactions[1].MsgTx().TxIn[0].Witness[0], resendTx2.MsgTx().TxIn[0].Witness[0]) // mine a block with those replacement transactions just to be sure they execute correctly - sendTransactions[1] = resendTx2 - blockWithOpReturnTransactions := tm.MineBlockWithTxs(t, sendTransactions) + blockWithOpReturnTransactions := tm.mineBlock(t) // block should have 2 transactions, 1 from submitter and 1 coinbase require.Equal(t, len(blockWithOpReturnTransactions.Transactions), 3) } diff --git a/e2etest/test_manager.go b/e2etest/test_manager.go index 0395cc02..d5b9936e 100644 --- a/e2etest/test_manager.go +++ b/e2etest/test_manager.go @@ -1,11 +1,12 @@ package e2etest import ( + "bytes" "context" - "encoding/binary" "encoding/hex" - "os" - "path/filepath" + "encoding/json" + "fmt" + "go.uber.org/zap" "testing" "time" @@ -16,167 +17,56 @@ 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/btcutil/hdkeychain" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/integration/rpctest" "github.com/btcsuite/btcd/rpcclient" "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) -// bticoin params used for testing var ( - netParams = &chaincfg.SimNetParams submitterAddrStr = "bbn1eppc73j56382wjn6nnq3quu5eye4pmm087xfdh" //nolint:unused babylonTag = []byte{1, 2, 3, 4} //nolint:unused babylonTagHex = hex.EncodeToString(babylonTag) //nolint:unused - // copy of the seed from btcd/integration/rpctest memWallet, this way we can - // import the same wallet in the btcd wallet - hdSeed = [chainhash.HashSize]byte{ - 0x79, 0xa6, 0x1a, 0xdb, 0xc6, 0xe5, 0xa2, 0xe1, - 0x39, 0xd2, 0x71, 0x3a, 0x54, 0x6e, 0xc7, 0xc8, - 0x75, 0x63, 0x2e, 0x75, 0xf1, 0xdf, 0x9c, 0x3f, - 0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - } - - // current number of active test nodes. This is necessary to replicate btcd rpctest.Harness - // methods of generating keys i.e with each started btcd node we increment this number - // by 1, and then use hdSeed || numTestInstances as the seed for generating keys - numTestInstances = 0 - - existingWalletFile = "wallet.db" - exisitngWalletPass = "pass" - walletTimeout = 86400 - eventuallyWaitTimeOut = 40 * time.Second eventuallyPollTime = 1 * time.Second + regtestParams = &chaincfg.RegressionNetParams ) -// keyToAddr maps the passed private to corresponding p2pkh address. -func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) { - serializedKey := key.PubKey().SerializeCompressed() - pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net) - if err != nil { - return nil, err - } - return pubKeyAddr.AddressPubKeyHash(), nil -} - func defaultVigilanteConfig() *config.Config { defaultConfig := config.DefaultConfig() - // Config setting necessary to connect btcd daemon - defaultConfig.BTC.NetParams = "simnet" - defaultConfig.BTC.Endpoint = "127.0.0.1:18556" + defaultConfig.BTC.NetParams = regtestParams.Name + defaultConfig.BTC.Endpoint = "127.0.0.1:18443" // Config setting necessary to connect btcwallet daemon - defaultConfig.BTC.BtcBackend = "btcd" - defaultConfig.BTC.WalletEndpoint = "127.0.0.1:18554" defaultConfig.BTC.WalletPassword = "pass" defaultConfig.BTC.Username = "user" defaultConfig.BTC.Password = "pass" - defaultConfig.BTC.DisableClientTLS = true - return defaultConfig -} - -func GetSpendingKeyAndAddress(id uint32) (*btcec.PrivateKey, btcutil.Address, error) { - var harnessHDSeed [chainhash.HashSize + 4]byte - copy(harnessHDSeed[:], hdSeed[:]) - // id used for our test wallet is always 0 - binary.BigEndian.PutUint32(harnessHDSeed[:chainhash.HashSize], id) - - hdRoot, err := hdkeychain.NewMaster(harnessHDSeed[:], netParams) - - if err != nil { - return nil, nil, err - } - - // The first child key from the hd root is reserved as the coinbase - // generation address. - coinbaseChild, err := hdRoot.Derive(0) - if err != nil { - return nil, nil, err - } - - coinbaseKey, err := coinbaseChild.ECPrivKey() - - if err != nil { - return nil, nil, err - } - - coinbaseAddr, err := keyToAddr(coinbaseKey, netParams) - if err != nil { - return nil, nil, err - } + defaultConfig.BTC.ZmqSeqEndpoint = config.DefaultZmqSeqEndpoint - return coinbaseKey, coinbaseAddr, nil + return defaultConfig } type TestManager struct { - MinerNode *rpctest.Harness - BtcWalletHandler *WalletHandler - BabylonHandler *BabylonNodeHandler - BabylonClient *bbnclient.Client - BTCClient *btcclient.Client - BTCWalletClient *btcclient.Client - Config *config.Config + TestRpcClient *rpcclient.Client + BitcoindHandler *BitcoindTestHandler + BabylonHandler *BabylonNodeHandler + BabylonClient *bbnclient.Client + BTCClient *btcclient.Client + Config *config.Config + WalletPrivKey *btcec.PrivateKey } -func initBTCWalletClient( - t *testing.T, - cfg *config.Config, - walletPrivKey *btcec.PrivateKey, - outputsToWaitFor int) *btcclient.Client { - - var client *btcclient.Client - - require.Eventually(t, func() bool { - btcWallet, err := btcclient.NewWallet(&cfg.BTC, logger) - if err != nil { - return false - } - - client = btcWallet - return true - - }, eventuallyWaitTimeOut, eventuallyPollTime) - - // lets wait until chain rpc becomes available - // poll time is increase here to avoid spamming the btcwallet rpc server - require.Eventually(t, func() bool { - if _, _, err := client.GetBestBlock(); err != nil { - return false - } - - return true - }, eventuallyWaitTimeOut, 1*time.Second) - - err := ImportWalletSpendingKey(t, client, walletPrivKey) - require.NoError(t, err) - - waitForNOutputs(t, client, outputsToWaitFor) - - return client -} - -func initBTCClientWithSubscriber(t *testing.T, cfg *config.Config, rpcClient *rpcclient.Client, connCfg *rpcclient.ConnConfig, blockEventChan chan *types.BlockEvent) *btcclient.Client { - btcCfg := &config.BTCConfig{ - NetParams: cfg.BTC.NetParams, - Username: connCfg.User, - Password: connCfg.Pass, - Endpoint: connCfg.Host, - DisableClientTLS: connCfg.DisableTLS, - } - client, err := btcclient.NewTestClientWithWsSubscriber(rpcClient, btcCfg, cfg.Common.RetrySleepTime, cfg.Common.MaxRetrySleepTime, blockEventChan) +func initBTCClientWithSubscriber(t *testing.T, cfg *config.Config) *btcclient.Client { + client, err := btcclient.NewWallet(&cfg.BTC, zap.NewNop()) require.NoError(t, err) - // lets wait until chain rpc becomes available + // let's wait until chain rpc becomes available // poll time is increase here to avoid spamming the rpc server require.Eventually(t, func() bool { - if _, _, err := client.GetBestBlock(); err != nil { + if _, err := client.GetBlockCount(); err != nil { log.Errorf("failed to get best block: %v", err) return false } @@ -188,82 +78,57 @@ func initBTCClientWithSubscriber(t *testing.T, cfg *config.Config, rpcClient *rp } // StartManager creates a test manager -// NOTE: if handlers.OnFilteredBlockConnected, handlers.OnFilteredBlockDisconnected -// and blockEventChan are all not nil, then the test manager will create a BTC -// client with a WebSocket subscriber -func StartManager( - t *testing.T, - numMatureOutputsInWallet uint32, - numbersOfOutputsToWaitForDuringInit int, - handlers *rpcclient.NotificationHandlers, - blockEventChan chan *types.BlockEvent) *TestManager { - args := []string{ - "--rejectnonstd", - "--txindex", - "--trickleinterval=100ms", - "--debuglevel=debug", - "--nowinservice", - // The miner will get banned and disconnected from the node if - // its requested data are not found. We add a nobanning flag to - // make sure they stay connected if it happens. - "--nobanning", - // Don't disconnect if a reply takes too long. - "--nostalldetect", - } +// NOTE: uses btc client with zmq +func StartManager(t *testing.T, numMatureOutputsInWallet uint32) *TestManager { + btcHandler := NewBitcoindHandler(t) + btcHandler.Start() + passphrase := "pass" + _ = btcHandler.CreateWallet("default", passphrase) - miner, err := rpctest.New(netParams, handlers, args, "") - require.NoError(t, err) + cfg := defaultVigilanteConfig() - privkey, _, err := GetSpendingKeyAndAddress(uint32(numTestInstances)) + testRpcClient, err := rpcclient.New(&rpcclient.ConnConfig{ + Host: cfg.BTC.Endpoint, + User: cfg.BTC.Username, + Pass: cfg.BTC.Password, + DisableTLS: true, + DisableConnectOnNew: true, + DisableAutoReconnect: false, + HTTPPostMode: true, + }, nil) require.NoError(t, err) - if err := miner.SetUp(true, numMatureOutputsInWallet); err != nil { - t.Fatalf("unable to set up mining node: %v", err) - } - - minerNodeRpcConfig := miner.RPCConfig() - certFile := minerNodeRpcConfig.Certificates - - currentDir, err := os.Getwd() + err = testRpcClient.WalletPassphrase(passphrase, 600) require.NoError(t, err) - walletPath := filepath.Join(currentDir, existingWalletFile) - // start Bitcoin wallet - wh, err := NewWalletHandler(certFile, walletPath, minerNodeRpcConfig.Host) - require.NoError(t, err) - err = wh.Start() + walletPrivKey, err := importPrivateKey(btcHandler) require.NoError(t, err) + blocksResponse := btcHandler.GenerateBlocks(int(numMatureOutputsInWallet)) - // Wait for wallet to re-index the outputs - time.Sleep(5 * time.Second) + btcClient := initBTCClientWithSubscriber(t, cfg) - cfg := defaultVigilanteConfig() - cfg.BTC.Endpoint = minerNodeRpcConfig.Host + var buff bytes.Buffer + err = regtestParams.GenesisBlock.Header.Serialize(&buff) + require.NoError(t, err) + baseHeaderHex := hex.EncodeToString(buff.Bytes()) - var btcClient *btcclient.Client - if handlers.OnFilteredBlockConnected != nil && handlers.OnFilteredBlockDisconnected != nil { - // BTC client with subscriber - btcClient = initBTCClientWithSubscriber(t, cfg, miner.Client, &minerNodeRpcConfig, blockEventChan) - } - // we always want BTC wallet client for sending txs - btcWalletClient := initBTCWalletClient( - t, - cfg, - privkey, - numbersOfOutputsToWaitForDuringInit, - ) + minerAddressDecoded, err := btcutil.DecodeAddress(blocksResponse.Address, regtestParams) + require.NoError(t, err) // start Babylon node - bh, err := NewBabylonNodeHandler() + bh, err := NewBabylonNodeHandler(baseHeaderHex, minerAddressDecoded.EncodeAddress()) require.NoError(t, err) err = bh.Start() require.NoError(t, err) + // create Babylon client cfg.Babylon.KeyDirectory = bh.GetNodeDataDir() - cfg.Babylon.Key = "test-spending-key" + cfg.Babylon.Key = "test-spending-key" // keyring to bbn node cfg.Babylon.GasAdjustment = 3.0 + babylonClient, err := bbnclient.New(&cfg.Babylon, nil) require.NoError(t, err) + // wait until Babylon is ready require.Eventually(t, func() bool { resp, err := babylonClient.CurrentEpoch() @@ -274,82 +139,54 @@ func StartManager( return true }, eventuallyWaitTimeOut, eventuallyPollTime) - numTestInstances++ - return &TestManager{ - MinerNode: miner, - BtcWalletHandler: wh, - BabylonHandler: bh, - BabylonClient: babylonClient, - BTCClient: btcClient, - BTCWalletClient: btcWalletClient, - Config: cfg, + TestRpcClient: testRpcClient, + BabylonHandler: bh, + BabylonClient: babylonClient, + BitcoindHandler: btcHandler, + BTCClient: btcClient, + Config: cfg, + WalletPrivKey: walletPrivKey, } } func (tm *TestManager) Stop(t *testing.T) { - err := tm.BtcWalletHandler.Stop() - require.NoError(t, err) - err = tm.MinerNode.TearDown() + err := tm.BabylonHandler.Stop() require.NoError(t, err) + if tm.BabylonClient.IsRunning() { - err = tm.BabylonClient.Stop() + err := tm.BabylonClient.Stop() require.NoError(t, err) } - err = tm.BabylonHandler.Stop() - require.NoError(t, err) } -func ImportWalletSpendingKey( - t *testing.T, - walletClient *btcclient.Client, - privKey *btcec.PrivateKey) error { +// mineBlock mines a single block +func (tm *TestManager) mineBlock(t *testing.T) *wire.MsgBlock { + resp := tm.BitcoindHandler.GenerateBlocks(1) - wifKey, err := btcutil.NewWIF(privKey, netParams, true) + hash, err := chainhash.NewHashFromStr(resp.Blocks[0]) require.NoError(t, err) - err = walletClient.WalletPassphrase(exisitngWalletPass, int64(walletTimeout)) - - if err != nil { - return err - } - - err = walletClient.ImportPrivKey(wifKey) - - if err != nil { - return err - } - - return nil -} - -// MineBlocksWithTxes mines a single block to include the specifies -// transactions only. -func (tm *TestManager) MineBlockWithTxs(t *testing.T, txs []*btcutil.Tx) *wire.MsgBlock { - var emptyTime time.Time - - // Generate a block. - b, err := tm.MinerNode.GenerateAndSubmitBlock(txs, -1, emptyTime) - require.NoError(t, err, "unable to mine block") - - block, err := tm.MinerNode.Client.GetBlock(b.Hash()) - require.NoError(t, err, "unable to get block") + header, err := tm.TestRpcClient.GetBlock(hash) + require.NoError(t, err) - return block + return header } func (tm *TestManager) MustGetBabylonSigner() string { return tm.BabylonClient.MustGetAddr() } +// RetrieveTransactionFromMempool fetches transactions from the mempool for the given hashes func (tm *TestManager) RetrieveTransactionFromMempool(t *testing.T, hashes []*chainhash.Hash) []*btcutil.Tx { - var txes []*btcutil.Tx + var txs []*btcutil.Tx for _, txHash := range hashes { tx, err := tm.BTCClient.GetRawTransaction(txHash) require.NoError(t, err) - txes = append(txes, tx) + txs = append(txs, tx) } - return txes + + return txs } func (tm *TestManager) InsertBTCHeadersToBabylon(headers []*wire.BlockHeader) (*pv.RelayerTxResponse, error) { @@ -368,18 +205,18 @@ func (tm *TestManager) InsertBTCHeadersToBabylon(headers []*wire.BlockHeader) (* } func (tm *TestManager) CatchUpBTCLightClient(t *testing.T) { - _, btcHeight, err := tm.MinerNode.Client.GetBestBlock() + btcHeight, err := tm.TestRpcClient.GetBlockCount() require.NoError(t, err) tipResp, err := tm.BabylonClient.BTCHeaderChainTip() require.NoError(t, err) btclcHeight := tipResp.Header.Height - headers := []*wire.BlockHeader{} + var headers []*wire.BlockHeader for i := int(btclcHeight + 1); i <= int(btcHeight); i++ { - hash, err := tm.MinerNode.Client.GetBlockHash(int64(i)) + hash, err := tm.TestRpcClient.GetBlockHash(int64(i)) require.NoError(t, err) - header, err := tm.MinerNode.Client.GetBlockHeader(hash) + header, err := tm.TestRpcClient.GetBlockHeader(hash) require.NoError(t, err) headers = append(headers, header) } @@ -388,14 +225,35 @@ func (tm *TestManager) CatchUpBTCLightClient(t *testing.T) { require.NoError(t, err) } -func waitForNOutputs(t *testing.T, walletClient *btcclient.Client, n int) { - require.Eventually(t, func() bool { - outputs, err := walletClient.ListUnspent() +func importPrivateKey(btcHandler *BitcoindTestHandler) (*btcec.PrivateKey, error) { + privKey, err := btcec.NewPrivateKey() + if err != nil { + return nil, err + } - if err != nil { - return false - } + wif, err := btcutil.NewWIF(privKey, regtestParams, true) + if err != nil { + return nil, err + } - return len(outputs) >= n - }, eventuallyWaitTimeOut, eventuallyPollTime) + // "combo" allows us to import a key and handle multiple types of btc scripts with a single descriptor command. + descriptor := fmt.Sprintf("combo(%s)", wif.String()) + + // Create the JSON descriptor object. + descJSON, err := json.Marshal([]map[string]interface{}{ + { + "desc": descriptor, + "active": true, + "timestamp": "now", // tells Bitcoind to start scanning from the current blockchain height + "label": "test key", + }, + }) + + if err != nil { + return nil, err + } + + btcHandler.ImportDescriptors(string(descJSON)) + + return privKey, nil } diff --git a/e2etest/test_manager_btcstaking.go b/e2etest/test_manager_btcstaking.go index a780aa77..c8b1bcdc 100644 --- a/e2etest/test_manager_btcstaking.go +++ b/e2etest/test_manager_btcstaking.go @@ -93,23 +93,20 @@ func (tm *TestManager) CreateBTCDelegation( require.NoError(t, err) stakingTimeBlocks := uint16(math.MaxUint16) // get top UTXO - topUnspentResult, _, err := tm.BTCWalletClient.GetHighUTXOAndSum() + topUnspentResult, _, err := tm.BTCClient.GetHighUTXOAndSum() require.NoError(t, err) - topUTXO, err := types.NewUTXO(topUnspentResult, netParams) + topUTXO, err := types.NewUTXO(topUnspentResult, regtestParams) require.NoError(t, err) // staking value stakingValue := int64(topUTXO.Amount) / 3 - // dump SK - wif, err := tm.BTCWalletClient.DumpPrivKey(topUTXO.Addr) - require.NoError(t, err) // generate legitimate BTC del stakingSlashingInfo := datagen.GenBTCStakingSlashingInfoWithOutPoint( r, t, - netParams, + regtestParams, topUTXO.GetOutPoint(), - wif.PrivKey, + tm.WalletPrivKey, []*btcec.PublicKey{fpPK}, covenantBtcPks, bsParams.Params.CovenantQuorum, @@ -121,7 +118,7 @@ func (tm *TestManager) CreateBTCDelegation( ) // sign staking tx and overwrite the staking tx to the signed version // NOTE: the tx hash has changed here since stakingMsgTx is pre-segwit - stakingMsgTx, signed, err := tm.BTCWalletClient.SignRawTransaction(stakingSlashingInfo.StakingTx) + stakingMsgTx, signed, err := tm.BTCClient.SignRawTransactionWithWallet(stakingSlashingInfo.StakingTx) require.NoError(t, err) require.True(t, signed) // overwrite staking tx @@ -140,12 +137,16 @@ func (tm *TestManager) CreateBTCDelegation( require.NoError(t, err) // send staking tx to Bitcoin node's mempool - _, err = tm.BTCWalletClient.SendRawTransaction(stakingMsgTx, true) + _, err = tm.BTCClient.SendRawTransaction(stakingMsgTx, true) require.NoError(t, err) - // mine a block with this tx, and insert it to Bitcoin / Babylon - mBlock := tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{stakingMsgTxHash})) + require.Eventually(t, func() bool { + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{stakingMsgTxHash})) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + mBlock := tm.mineBlock(t) require.Equal(t, 2, len(mBlock.Transactions)) + // wait until staking tx is on Bitcoin require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(stakingMsgTxHash) @@ -159,14 +160,14 @@ func (tm *TestManager) CreateBTCDelegation( require.NoError(t, err) btccParams := btccParamsResp.Params for i := 0; i < int(btccParams.BtcConfirmationDepth); i++ { - tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{})) + tm.mineBlock(t) } stakingOutIdx, err := outIdx(stakingSlashingInfo.StakingTx, stakingSlashingInfo.StakingInfo.StakingOutput) require.NoError(t, err) // create PoP - pop, err := bstypes.NewPoPBTC(addr, wif.PrivKey) + pop, err := bstypes.NewPoPBTC(addr, tm.WalletPrivKey) require.NoError(t, err) slashingSpendPath, err := stakingSlashingInfo.StakingInfo.SlashingPathSpendInfo() require.NoError(t, err) @@ -176,7 +177,7 @@ func (tm *TestManager) CreateBTCDelegation( stakingMsgTx, stakingOutIdx, slashingSpendPath.GetPkScriptPath(), - wif.PrivKey, + tm.WalletPrivKey, ) require.NoError(t, err) @@ -186,8 +187,8 @@ func (tm *TestManager) CreateBTCDelegation( unbondingSlashingInfo := datagen.GenBTCUnbondingSlashingInfo( r, t, - netParams, - wif.PrivKey, + regtestParams, + tm.WalletPrivKey, []*btcec.PublicKey{fpPK}, covenantBtcPks, bsParams.Params.CovenantQuorum, @@ -208,7 +209,7 @@ func (tm *TestManager) CreateBTCDelegation( unbondingSlashingInfo.UnbondingTx, 0, // Only one output in the unbonding tx unbondingSlashingPathSpendInfo.GetPkScriptPath(), - wif.PrivKey, + tm.WalletPrivKey, ) require.NoError(t, err) @@ -219,7 +220,7 @@ func (tm *TestManager) CreateBTCDelegation( msgBTCDel := &bstypes.MsgCreateBTCDelegation{ StakerAddr: signerAddr, Pop: pop, - BtcPk: bbn.NewBIP340PubKeyFromBTCPK(wif.PrivKey.PubKey()), + BtcPk: bbn.NewBIP340PubKeyFromBTCPK(tm.WalletPrivKey.PubKey()), FpBtcPkList: []bbn.BIP340PubKey{*bbn.NewBIP340PubKeyFromBTCPK(fpPK)}, StakingTime: uint32(stakingTimeBlocks), StakingValue: stakingValue, @@ -288,7 +289,7 @@ func (tm *TestManager) CreateBTCDelegation( require.NoError(t, err) t.Logf("submitted covenant signature") - return stakingSlashingInfo, unbondingSlashingInfo, wif.PrivKey + return stakingSlashingInfo, unbondingSlashingInfo, tm.WalletPrivKey } func (tm *TestManager) Undelegate( @@ -334,16 +335,22 @@ func (tm *TestManager) Undelegate( unbondingSlashingInfo.UnbondingTx.TxIn[0].Witness = witness // send unbonding tx to Bitcoin node's mempool - unbondingTxHash, err := tm.BTCWalletClient.SendRawTransaction(unbondingSlashingInfo.UnbondingTx, true) + unbondingTxHash, err := tm.BTCClient.SendRawTransaction(unbondingSlashingInfo.UnbondingTx, true) require.NoError(t, err) require.Eventually(t, func() bool { _, err := tm.BTCClient.GetRawTransaction(unbondingTxHash) return err == nil }, eventuallyWaitTimeOut, eventuallyPollTime) t.Logf("submitted unbonding tx with hash %s", unbondingTxHash.String()) + // mine a block with this tx, and insert it to Bitcoin - mBlock := tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingTxHash})) + require.Eventually(t, func() bool { + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{unbondingTxHash})) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + mBlock := tm.mineBlock(t) require.Equal(t, 2, len(mBlock.Transactions)) + // wait until unbonding tx is on Bitcoin require.Eventually(t, func() bool { resp, err := tm.BTCClient.GetRawTransactionVerbose(unbondingTxHash) diff --git a/e2etest/unbondingwatcher_e2e_test.go b/e2etest/unbondingwatcher_e2e_test.go index 53ef2fcf..d4ba0d98 100644 --- a/e2etest/unbondingwatcher_e2e_test.go +++ b/e2etest/unbondingwatcher_e2e_test.go @@ -4,7 +4,7 @@ package e2etest import ( - "encoding/hex" + "go.uber.org/zap" "testing" "time" @@ -13,13 +13,9 @@ 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/types" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/rpcclient" - "github.com/btcsuite/btcd/wire" "github.com/stretchr/testify/require" ) @@ -27,39 +23,16 @@ func TestUnbondingWatcher(t *testing.T) { // segwit is activated at height 300. It's needed by staking/slashing tx numMatureOutputs := uint32(300) - submittedTxs := []*chainhash.Hash{} - blockEventChan := make(chan *types.BlockEvent, 1000) - handlers := &rpcclient.NotificationHandlers{ - OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txs []*btcutil.Tx) { - log.Debugf("Block %v at height %d has been connected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockConnected, height, header) - }, - OnFilteredBlockDisconnected: func(height int32, header *wire.BlockHeader) { - log.Debugf("Block %v at height %d has been disconnected at time %v", header.BlockHash(), height, header.Timestamp) - blockEventChan <- types.NewBlockEvent(types.BlockDisconnected, height, header) - }, - OnTxAccepted: func(hash *chainhash.Hash, amount btcutil.Amount) { - submittedTxs = append(submittedTxs, hash) - }, - } - - tm := StartManager(t, numMatureOutputs, 2, handlers, blockEventChan) - // this is necessary to receive notifications about new transactions entering mempool - err := tm.MinerNode.Client.NotifyNewTransactions(false) - require.NoError(t, err) - err = tm.MinerNode.Client.NotifyBlocks() - require.NoError(t, err) + tm := StartManager(t, numMatureOutputs) defer tm.Stop(t) // Insert all existing BTC headers to babylon node tm.CatchUpBTCLightClient(t) emptyHintCache := btcclient.EmptyHintCache{} - // 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, hex.EncodeToString(tm.MinerNode.RPCConfig().Certificates)), - &chaincfg.SimNetParams, + btcclient.ToBitcoindConfig(tm.Config.BTC), + &chaincfg.RegressionNetParams, &emptyHintCache, ) require.NoError(t, err) @@ -70,10 +43,7 @@ func TestUnbondingWatcher(t *testing.T) { commonCfg := config.DefaultCommonConfig() bstCfg := config.DefaultBTCStakingTrackerConfig() bstCfg.CheckDelegationsInterval = 1 * time.Second - logger, err := config.NewRootLogger("auto", "debug") - require.NoError(t, err) - - metrics := metrics.NewBTCStakingTrackerMetrics() + stakingTrackerMetrics := metrics.NewBTCStakingTrackerMetrics() bsTracker := bst.NewBTCSTakingTracker( tm.BTCClient, @@ -81,24 +51,23 @@ func TestUnbondingWatcher(t *testing.T) { tm.BabylonClient, &bstCfg, &commonCfg, - logger, - metrics, + zap.NewNop(), + stakingTrackerMetrics, ) bsTracker.Start() defer bsTracker.Stop() // set up a finality provider _, fpSK := tm.CreateFinalityProvider(t) - logger.Info("created finality provider") // set up a BTC delegation stakingSlashingInfo, unbondingSlashingInfo, delSK := tm.CreateBTCDelegation(t, fpSK) - logger.Info("created BTC delegation") // Staker unbonds by directly sending tx to btc network. Watcher should detect it and report to babylon. unbondingPathSpendInfo, err := stakingSlashingInfo.StakingInfo.UnbondingPathSpendInfo() require.NoError(t, err) stakingOutIdx, err := outIdx(unbondingSlashingInfo.UnbondingTx, unbondingSlashingInfo.UnbondingInfo.UnbondingOutput) require.NoError(t, err) + unbondingTxSchnorrSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( unbondingSlashingInfo.UnbondingTx, stakingSlashingInfo.StakingTx, @@ -107,22 +76,30 @@ func TestUnbondingWatcher(t *testing.T) { delSK, ) require.NoError(t, err) + resp, err := tm.BabylonClient.BTCDelegation(stakingSlashingInfo.StakingTx.TxHash().String()) require.NoError(t, err) + covenantSigs := resp.BtcDelegation.UndelegationResponse.CovenantUnbondingSigList witness, err := unbondingPathSpendInfo.CreateUnbondingPathWitness( []*schnorr.Signature{covenantSigs[0].Sig.MustToBTCSig()}, unbondingTxSchnorrSig, ) unbondingSlashingInfo.UnbondingTx.TxIn[0].Witness = witness + // Send unbonding tx to Bitcoin - _, err = tm.BTCWalletClient.SendRawTransaction(unbondingSlashingInfo.UnbondingTx, true) + _, err = tm.BTCClient.SendRawTransaction(unbondingSlashingInfo.UnbondingTx, true) require.NoError(t, err) + // mine a block with this tx, and insert it to Bitcoin unbondingTxHash := unbondingSlashingInfo.UnbondingTx.TxHash() t.Logf("submitted unbonding tx with hash %s", unbondingTxHash.String()) - mBlock := tm.MineBlockWithTxs(t, tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{&unbondingTxHash})) - require.Equal(t, 2, len(mBlock.Transactions)) + require.Eventually(t, func() bool { + return len(tm.RetrieveTransactionFromMempool(t, []*chainhash.Hash{&unbondingTxHash})) == 1 + }, eventuallyWaitTimeOut, eventuallyPollTime) + + minedBlock := tm.mineBlock(t) + require.Equal(t, 2, len(minedBlock.Transactions)) require.Eventually(t, func() bool { resp, err := tm.BabylonClient.BTCDelegation(stakingSlashingInfo.StakingTx.TxHash().String()) diff --git a/go.mod b/go.mod index ee3c7c95..71a53725 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/babylonlabs-io/vigilante -go 1.21 - -toolchain go1.21.4 +go 1.23 require ( cosmossdk.io/errors v1.0.1 @@ -19,7 +17,6 @@ require ( github.com/cometbft/cometbft v0.38.7 github.com/cosmos/cosmos-sdk v0.50.6 github.com/cosmos/relayer/v2 v2.5.1 - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/gogo/protobuf v1.3.3 github.com/golang/mock v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 @@ -28,7 +25,7 @@ require ( github.com/jinzhu/copier v0.3.5 github.com/jsternberg/zap-logfmt v1.3.0 github.com/lightningnetwork/lnd v0.16.4-beta.rc1 - github.com/pebbe/zmq4 v1.2.9 + github.com/ory/dockertest/v3 v3.9.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.0 github.com/spf13/cobra v1.8.0 @@ -63,10 +60,14 @@ require ( cosmossdk.io/x/upgrade v0.1.1 // indirect filippo.io/edwards25519 v1.0.0 // indirect github.com/99designs/keyring v1.2.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/CosmWasm/wasmd v0.51.0 // indirect github.com/CosmWasm/wasmvm/v2 v2.0.1 // indirect github.com/DataDog/datadog-go v3.2.0+incompatible // indirect 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 @@ -74,7 +75,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 @@ -93,6 +98,7 @@ require ( github.com/cometbft/cometbft-db v0.9.1 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/containerd/continuity v0.3.0 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect @@ -113,24 +119,29 @@ require ( github.com/danieljoos/wincred v1.1.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect github.com/decred/dcrd/lru v1.0.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect github.com/distribution/reference v0.5.0 // indirect + github.com/docker/cli v23.0.1+incompatible // indirect github.com/docker/docker v23.0.8+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvsekhvalnov/jose2go v1.6.0 // indirect github.com/emicklei/dot v1.6.1 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect - github.com/ethereum/go-ethereum v1.13.14 // indirect + github.com/ethereum/go-ethereum v1.13.15 // indirect github.com/fatih/color v1.15.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect 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 @@ -148,6 +159,7 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/orderedcode v0.0.1 // indirect github.com/google/s2a-go v0.1.7 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect @@ -158,7 +170,7 @@ require ( github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.3 // indirect + github.com/hashicorp/go-getter v1.7.5 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-metrics v0.5.3 // indirect @@ -172,6 +184,7 @@ require ( github.com/holiman/uint256 v1.2.4 // indirect github.com/huandu/skiplist v1.2.0 // indirect github.com/iancoleman/strcase v0.3.0 // indirect + github.com/imdario/mergo v0.3.13 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect @@ -201,6 +214,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 @@ -209,6 +223,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 @@ -220,6 +235,7 @@ require ( github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mtibben/percent v0.2.1 // indirect @@ -227,6 +243,8 @@ require ( github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect github.com/oklog/run v1.1.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2 // indirect + github.com/opencontainers/runc v1.1.5 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect @@ -259,6 +277,9 @@ require ( github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect github.com/ulikunitz/xz v0.5.11 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect github.com/zondax/hid v0.9.2 // indirect diff --git a/go.sum b/go.sum index 32b26993..aa5d1149 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= @@ -361,6 +366,7 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= @@ -371,6 +377,7 @@ github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObk github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= +github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= @@ -412,6 +419,7 @@ github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/Yj github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -422,6 +430,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= @@ -469,6 +478,9 @@ github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6 github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= @@ -498,10 +510,13 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= +github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/docker v23.0.8+incompatible h1:z4ZCIwfqHgOEwhxmAWugSL1PFtPQmLP60EVhJYJPaX8= github.com/docker/docker v23.0.8+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= @@ -535,8 +550,8 @@ github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.13.14 h1:EwiY3FZP94derMCIam1iW4HFVrSgIcpsu0HwTQtm6CQ= -github.com/ethereum/go-ethereum v1.13.14/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= +github.com/ethereum/go-ethereum v1.13.15 h1:U7sSGYGo4SPjP6iNIifNoyIAiNjrmQkz6EwQG+/EZWo= +github.com/ethereum/go-ethereum v1.13.15/go.mod h1:TN8ZiHrdJwSe8Cb6x+p0hs5CxhJZPbqB7hHkaUXcmIU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= @@ -549,6 +564,7 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -601,6 +617,8 @@ github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= @@ -614,6 +632,7 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -724,6 +743,8 @@ github.com/google/pprof v0.0.0-20230228050547-1710fef4ab10/go.mod h1:79YE0hCXdHa github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -780,8 +801,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= -github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -837,6 +858,8 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/improbable-eng/grpc-web v0.15.0/go.mod h1:1sy9HKV4Jt9aEs9JSnkWlRJPuPtwNr0l57L4f878wP8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -950,6 +973,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= @@ -1006,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= @@ -1059,6 +1084,9 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= +github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -1066,6 +1094,7 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -1118,6 +1147,8 @@ github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7X github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/runc v1.1.5 h1:L44KXEpKmfWDcS02aeGm8QNTFXTo2D+8MYGDIJ/GDEs= github.com/opencontainers/runc v1.1.5/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -1128,13 +1159,13 @@ github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnh github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= github.com/ory/dockertest v3.3.5+incompatible h1:iLLK6SQwIhcbrG783Dghaaa3WPzGc+4Emza6EbVUUGA= github.com/ory/dockertest v3.3.5+incompatible/go.mod h1:1vX4m9wsvi00u5bseYwXaSnhNrne+V0E6LAcBILJdPs= +github.com/ory/dockertest/v3 v3.9.1 h1:v4dkG+dlu76goxMiTT2j8zV7s4oPPEppKT8K8p2f1kY= +github.com/ory/dockertest/v3 v3.9.1/go.mod h1:42Ir9hmvaAPm0Mgibk6mBPi7SFvTXxEcnztDYOJ//uM= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 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= @@ -1228,6 +1259,7 @@ github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71e github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible h1:Bn1aCHHRnjv4Bl16T8rcaFjYSrGrIZvpiGO6P3Q4GpU= github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= @@ -1239,6 +1271,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -1299,6 +1332,7 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4= github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= @@ -1329,8 +1363,17 @@ github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/vulpine-io/io-test v1.0.0 h1:Ot8vMh+ssm1VWDAwJ3U4C5qG9aRnr5YfQFZPNZBAUGI= github.com/vulpine-io/io-test v1.0.0/go.mod h1:X1I+p5GCxVX9m4nFd1HBtr2bVX9v1ZE6x8w+Obt36AU= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= @@ -1525,6 +1568,7 @@ golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= @@ -1609,6 +1653,7 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1619,6 +1664,7 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1650,6 +1696,7 @@ golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1668,9 +1715,11 @@ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2067,6 +2116,7 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= diff --git a/monitor/btcscanner/block_handler.go b/monitor/btcscanner/block_handler.go index 7054f9a4..704d1b9b 100644 --- a/monitor/btcscanner/block_handler.go +++ b/monitor/btcscanner/block_handler.go @@ -3,75 +3,90 @@ package btcscanner import ( "errors" "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 (bs *BtcScanner) blockEventHandler() { +// 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 { 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 { - bs.logger.Warnf("failed to handle a connected block at height %d: %s, "+ - "need to restart the bootstrapping process", event.Height, err.Error()) - if bs.Synced.Swap(false) { - bs.Bootstrap() - } - } - } else if event.EventType == types.BlockDisconnected { - err := bs.handleDisconnectedBlocks(event) - if 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()) - 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()) + 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(event *types.BlockEvent) error { - if !bs.Synced.Load() { - return errors.New("the btc scanner is not synced") - } - - // get the block from hash - blockHash := event.Header.BlockHash() - ib, _, err := bs.BtcClient.GetBlockByHash(&blockHash) - if err != nil { - // failing to request the block, which means a bug - panic(err) - } - +func (bs *BtcScanner) handleNewBlock(height int32, header *wire.BlockHeader) error { // 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(fmt.Errorf("failed to request block by hash: %s", blockHash.String())) + } + // otherwise, add the block to the cache bs.UnconfirmedBlockCache.Add(ib) @@ -94,24 +109,3 @@ func (bs *BtcScanner) handleConnectedBlocks(event *types.BlockEvent) error { return nil } - -// handleDisconnectedBlocks handles disconnected blocks from the BTC client. -func (bs *BtcScanner) handleDisconnectedBlocks(event *types.BlockEvent) 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 event.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 5ed6ebe4..16da44ab 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 @@ -38,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{} @@ -49,7 +49,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,14 +65,14 @@ 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, ConfirmedBlocksChan: confirmedBlocksChan, blockHeaderChan: headersChan, checkpointsChan: ckptsChan, - Synced: atomic.NewBool(false), Started: atomic.NewBool(false), quit: make(chan struct{}), }, nil @@ -84,17 +85,17 @@ func (bs *BtcScanner) Start() { return } - // 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") + if err := bs.btcNotifier.Start(); err != nil { + bs.logger.Errorf("Failed starting notifier") + return + } + // start handling new blocks bs.wg.Add(1) - go bs.blockEventHandler() + go bs.bootstrapAndBlockEventHandler() for bs.Started.Load() { select { @@ -128,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 74b8f195..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) { @@ -30,7 +28,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))). @@ -45,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) @@ -58,6 +54,5 @@ func FuzzBootStrap(f *testing.F) { } }() btcScanner.Bootstrap() - require.True(t, btcScanner.Synced.Load()) }) } diff --git a/monitor/monitor.go b/monitor/monitor.go index a6d13824..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, ) @@ -197,8 +200,10 @@ func (m *Monitor) GetCurrentEpoch() uint64 { func (m *Monitor) VerifyCheckpoint(btcCkpt *checkpointingtypes.RawCheckpoint) error { // check whether the epoch number of the checkpoint equals to the current epoch number if m.GetCurrentEpoch() != btcCkpt.EpochNum { - return errors.Wrapf(types.ErrInvalidEpochNum, fmt.Sprintf("found a checkpoint with epoch %v, but the monitor expects epoch %v", - btcCkpt.EpochNum, m.GetCurrentEpoch())) + return errors.Wrapf(types.ErrInvalidEpochNum, + "found a checkpoint with epoch %v, but the monitor expects epoch %v", + btcCkpt.EpochNum, m.GetCurrentEpoch()) + } // verify BLS sig of the BTC checkpoint err := m.curEpoch.VerifyMultiSig(btcCkpt) @@ -221,8 +226,9 @@ func (m *Monitor) VerifyCheckpoint(btcCkpt *checkpointingtypes.RawCheckpoint) er } // check whether the checkpoint from Babylon has the same BlockHash of the BTC checkpoint if !ckpt.BlockHash.Equal(*btcCkpt.BlockHash) { - return errors.Wrapf(types.ErrInconsistentBlockHash, fmt.Sprintf("Babylon checkpoint's BlockHash %s, BTC checkpoint's BlockHash %s", - ckpt.BlockHash.String(), btcCkpt.BlockHash)) + return errors.Wrapf(types.ErrInconsistentBlockHash, + "Babylon checkpoint's BlockHash %s, BTC checkpoint's BlockHash %s", + ckpt.BlockHash.String(), btcCkpt.BlockHash) } return nil } diff --git a/reporter/block_handler.go b/reporter/block_handler.go index 1927d78a..f14dea32 100644 --- a/reporter/block_handler.go +++ b/reporter/block_handler.go @@ -2,35 +2,30 @@ package reporter 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() + 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 } - var errorRequiringBootstrap error - if event.EventType == types.BlockConnected { - errorRequiringBootstrap = r.handleConnectedBlocks(event) - } else if event.EventType == types.BlockDisconnected { - errorRequiringBootstrap = r.handleDisconnectedBlocks(event) + 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() } - - 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 @@ -38,136 +33,67 @@ func (r *Reporter) blockEventHandler() { } } -// handleConnectedBlocks handles connected blocks from the BTC client. -func (r *Reporter) handleConnectedBlocks(event *types.BlockEvent) 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 event.Height < firstCacheBlock.Height { + + if cacheTip.Height >= 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 } - // 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(event.Height)); b != nil { - if b.BlockHash() == event.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", - event.Height, - event.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 := event.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) + // 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 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(event *types.BlockEvent) 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 event.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 5e45c678..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 @@ -81,14 +78,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() - } - consistencyInfo, err := r.checkConsistency() - if err != nil { return err } @@ -132,6 +122,7 @@ func (r *Reporter) bootstrap(skipBlockSubscription bool) error { } r.logger.Info("Successfully finished bootstrapping") + return nil } @@ -153,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, @@ -226,6 +217,7 @@ func (r *Reporter) initBTCCache() error { if err = r.btcCache.Init(ibs); err != nil { panic(err) } + return nil } @@ -316,5 +308,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 } 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/reporter.go b/reporter/reporter.go index 5a5bfb64..5c2f757c 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 @@ -30,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 @@ -45,6 +46,7 @@ func New( parentLogger *zap.Logger, btcClient btcclient.BTCClient, babylonClient BabylonClient, + btcNotifier notifier.ChainNotifier, retrySleepTime, maxRetrySleepTime time.Duration, metrics *metrics.ReporterMetrics, @@ -81,8 +83,8 @@ func New( maxRetrySleepTime: maxRetrySleepTime, btcClient: btcClient, babylonClient: babylonClient, + btcNotifier: btcNotifier, CheckpointCache: ckptCache, - reorgList: newReorgList(), btcConfirmationDepth: k, checkpointFinalizationTimeout: w, metrics: metrics, @@ -108,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() 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 { 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(), diff --git a/sample-vigilante.yml b/sample-vigilante.yml index 03d0b2da..076733e8 100644 --- a/sample-vigilante.yml +++ b/sample-vigilante.yml @@ -21,7 +21,6 @@ btc: username: rpcuser password: rpcpass reconnect-attempts: 3 - btc-backend: btcd # {btcd, bitcoind} zmq-endpoint: ~ # use tcp://127.0.0.1:29000 if btc-backend is bitcoind babylon: key: node0 diff --git a/submitter/relayer/estimator.go b/submitter/relayer/estimator.go index 33b2ec32..a9a395d4 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: true, + } + + 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/relayer/relayer.go b/submitter/relayer/relayer.go index 615921d8..4d167935 100644 --- a/submitter/relayer/relayer.go +++ b/submitter/relayer/relayer.go @@ -3,7 +3,9 @@ package relayer import ( "bytes" "encoding/hex" + "errors" "fmt" + "github.com/btcsuite/btcd/btcjson" "math" "strconv" "time" @@ -15,7 +17,6 @@ import ( "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/jinzhu/copier" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "go.uber.org/zap" @@ -25,6 +26,11 @@ import ( "github.com/babylonlabs-io/vigilante/types" ) +const ( + changePosition = 1 + dustThreshold btcutil.Amount = 546 +) + type Relayer struct { chainfee.Estimator btcclient.BTCWallet @@ -49,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(), } } @@ -74,8 +81,8 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp return nil } - if rl.lastSubmittedCheckpoint == nil || rl.lastSubmittedCheckpoint.Epoch < ckptEpoch { - 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 { @@ -84,6 +91,16 @@ func (rl *Relayer) SendCheckpointToBTC(ckpt *ckpttypes.RawCheckpointWithMetaResp rl.lastSubmittedCheckpoint = submittedCheckpoint + return nil + } 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 { + return err + } + + rl.lastSubmittedCheckpoint = submittedCheckpoint + return nil } @@ -138,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 @@ -154,13 +181,7 @@ func (rl *Relayer) shouldResendCheckpoint(ckptInfo *types.CheckpointInfo, bumped // based on the current BTC load, considering both tx sizes // the result is multiplied by ResubmitFeeMultiplier set in config func (rl *Relayer) calculateBumpedFee(ckptInfo *types.CheckpointInfo) btcutil.Amount { - feeRate := rl.getFeeRate() - newTx1Fee := feeRate.FeeForVSize(ckptInfo.Tx1.Size) - newTx2Fee := feeRate.FeeForVSize(ckptInfo.Tx2.Size) - // minus the old fee of the first transaction because we do not want to pay again for the first transaction - bumpedFee := newTx1Fee + newTx2Fee - ckptInfo.Tx1.Fee - - return bumpedFee.MulF64(float64(rl.config.ResubmitFeeMultiplier)) + return ckptInfo.Tx2.Fee.MulF64(rl.config.ResubmitFeeMultiplier) } // resendSecondTxOfCheckpointToBTC resends the second tx of the checkpoint with bumpedFee @@ -168,28 +189,31 @@ func (rl *Relayer) resendSecondTxOfCheckpointToBTC(tx2 *types.BtcTxInfo, bumpedF // set output value of the second tx to be the balance minus the bumped fee // if the bumped fee is higher than the balance, then set the bumped fee to // be equal to the balance to ensure the output value is not negative - balance := tx2.Utxo.Amount + balance := btcutil.Amount(tx2.Tx.TxOut[changePosition].Value) + + // todo: revise this as this means we will end up with output with value 0 that will be rejected by bitcoind as dust output. if bumpedFee > balance { rl.logger.Debugf("the bumped fee %v Satoshis for the second tx is more than UTXO amount %v Satoshis", bumpedFee, balance) bumpedFee = balance } - tx2.Tx.TxOut[1].Value = int64(balance - bumpedFee) + + tx2.Tx.TxOut[changePosition].Value = int64(balance - bumpedFee) // resign the tx as the output is changed - tx, err := rl.dumpPrivKeyAndSignTx(tx2.Tx, tx2.Utxo) + tx, err := rl.signTx(tx2.Tx) if err != nil { return nil, err } - txid, err := rl.sendTxToBTC(tx) + txID, err := rl.sendTxToBTC(tx) if err != nil { return nil, err } // update tx info tx2.Fee = bumpedFee - tx2.TxId = txid + tx2.TxId = txID return tx2, nil } @@ -217,85 +241,85 @@ func (rl *Relayer) calcMinRelayFee(txVirtualSize int64) btcutil.Amount { return minRelayFee } -func (rl *Relayer) dumpPrivKeyAndSignTx(tx *wire.MsgTx, utxo *types.UTXO) (*wire.MsgTx, error) { - // get private key - err := rl.WalletPassphrase(rl.GetWalletPass(), rl.GetWalletLockTime()) - if err != nil { +func (rl *Relayer) signTx(tx *wire.MsgTx) (*wire.MsgTx, error) { + // unlock the wallet + if err := rl.WalletPassphrase(rl.GetWalletPass(), rl.GetWalletLockTime()); err != nil { return nil, err } - wif, err := rl.DumpPrivKey(utxo.Addr) - if err != nil { - return nil, err - } - // add signature/witness depending on the type of the previous address - // if not segwit, add signature; otherwise, add witness - segwit, err := isSegWit(utxo.Addr) + + signedTx, allSigned, err := rl.BTCWallet.SignRawTransactionWithWallet(tx) if err != nil { return nil, err } - // add unlocking script into the input of the tx - tx, err = completeTxIn(tx, segwit, wif.PrivKey, utxo) - if err != nil { - return nil, err + + if !allSigned { + return nil, errors.New("transaction is only partially signed") } - return tx, nil + 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 - } - - utxo, err := rl.PickHighUTXO() - if err != nil { - return nil, err - } - - rl.logger.Debugf("Found one unspent tx with sufficient amount: %v", utxo.TxID) - - tx1, tx2, err := rl.ChainTwoTxAndSend( - utxo, - data1, - data2, - ) - if err != nil { - return nil, err + return nil, nil, err } - // 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 - time.Sleep(1 * time.Second) + // Return the encoded data + return data1, data2, nil +} +func (rl *Relayer) logAndRecordCheckpointMetrics(tx1, tx2 *types.BtcTxInfo, epochNum uint64) { + // 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, @@ -305,95 +329,96 @@ func (rl *Relayer) convertCkptToTwoTxAndSubmit(ckpt *ckpttypes.RawCheckpointResp }, nil } -// ChainTwoTxAndSend consumes one utxo and build two chaining txs: -// the second tx consumes the output of the first tx -func (rl *Relayer) ChainTwoTxAndSend( - utxo *types.UTXO, - 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( - utxo, - data1, - ) +// 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 { - return nil, nil, fmt.Errorf("failed to add data to tx1: %w", err) + return 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) + tx1 := rl.lastSubmittedCheckpoint.Tx1 + if tx1 == nil { + return nil, fmt.Errorf("tx1 is nil") // shouldn't happen, sanity check } - changeUtxo := &types.UTXO{ - TxID: tx1.TxId, - Vout: 1, - ScriptPK: tx1.Tx.TxOut[1].PkScript, - Amount: btcutil.Amount(tx1.Tx.TxOut[1].Value), - Addr: tx1.ChangeAddress, + tx2, err := rl.buildAndSendTx(data2, tx1.Tx) + if err != nil { + return nil, err } - // 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( - changeUtxo, - data2, - ) + 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, nil, fmt.Errorf("failed to add data to tx2: %w", err) + return nil, fmt.Errorf("failed to add data to tx: %w", err) } - tx2.TxId, err = rl.sendTxToBTC(tx2.Tx) + tx.TxId, err = rl.sendTxToBTC(tx.Tx) if err != nil { - return nil, nil, fmt.Errorf("failed to send tx2 to BTC: %w", err) + return nil, fmt.Errorf("failed to send tx to BTC: %w", err) } - // TODO: if tx1 succeeds but tx2 fails, we should not resent tx1 - - return tx1, tx2, nil + return tx, nil } -// PickHighUTXO picks a UTXO that has the highest amount -func (rl *Relayer) PickHighUTXO() (*types.UTXO, error) { - // get the highest UTXO and UTXOs' sum in the list - topUTXO, sum, err := rl.BTCWallet.GetHighUTXOAndSum() +// 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 + + tx1, err := rl.buildAndSendTx(data1, nil) if err != nil { - return nil, err + return nil, nil, err } - utxo, err := types.NewUTXO(topUTXO, rl.GetNetParams()) + + // 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 := rl.buildAndSendTx(data2, tx1.Tx) if err != nil { - return nil, fmt.Errorf("failed to convert ListUnspentResult to UTXO: %w", err) + return nil, nil, err } - rl.logger.Debugf("pick utxo with id: %v, amount: %v, confirmations: %v", utxo.TxID, utxo.Amount, topUTXO.Confirmations) - // record metrics of UTXOs' sum - rl.metrics.AvailableBTCBalance.Set(sum) - - return utxo, nil + return tx1, tx2, nil } -// buildTxWithData builds a tx with data inserted as OP_RETURN -// note that OP_RETURN is set as the first output of the tx (index 0) -// and the rest of the balance is sent to a new change address -// as the second output with index 1 -func (rl *Relayer) buildTxWithData( - utxo *types.UTXO, - data []byte, -) (*types.BtcTxInfo, error) { - rl.logger.Debugf("Building a BTC tx using %v with data %x", utxo.TxID.String(), data) +// buildTxWithData constructs a Bitcoin transaction with custom data inserted as an OP_RETURN output. +// If `firstTx` is provided, it uses its transaction ID and a predefined output index (`changePosition`) +// to create an input for the new transaction. The OP_RETURN output is added as the first output (index 0). +// +// This function also ensures that the transaction fee is sufficient and signs the transaction before returning it. +// If the UTXO value is insufficient to cover the fee or if the change amount falls below the dust threshold, +// an error is returned. +// +// Parameters: +// - data: The custom data to be inserted into the transaction as an OP_RETURN output. +// - firstTx: An optional transaction used to create an input for the new transaction. +func (rl *Relayer) buildTxWithData(data []byte, firstTx *wire.MsgTx) (*types.BtcTxInfo, error) { tx := wire.NewMsgTx(wire.TxVersion) - outPoint := wire.NewOutPoint(utxo.TxID, utxo.Vout) - txIn := wire.NewTxIn(outPoint, nil, nil) - // Enable replace-by-fee - // See https://river.com/learn/terms/r/replace-by-fee-rbf - txIn.Sequence = math.MaxUint32 - 2 - tx.AddTxIn(txIn) + if firstTx != nil { + txID := firstTx.TxHash() + outPoint := wire.NewOutPoint(&txID, changePosition) + txIn := wire.NewTxIn(outPoint, nil, nil) + // Enable replace-by-fee, see https://river.com/learn/terms/r/replace-by-fee-rbf + txIn.Sequence = math.MaxUint32 - 2 + tx.AddTxIn(txIn) + } - // build txout for data + // build txOut for data builder := txscript.NewScriptBuilder() dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() if err != nil { @@ -401,61 +426,79 @@ func (rl *Relayer) buildTxWithData( } tx.AddTxOut(wire.NewTxOut(0, dataScript)) - // build txout for change - changeAddr, err := rl.GetChangeAddress() - if err != nil { - return nil, fmt.Errorf("failed to get change address: %w", err) - } - rl.logger.Debugf("Got a change address %v", changeAddr.String()) - changeScript, err := txscript.PayToAddrScript(changeAddr) + changePosition := 1 // must declare here as you cannot take address of const needed bellow + feeRate := btcutil.Amount(rl.getFeeRate()).ToBTC() + rawTxResult, err := rl.BTCWallet.FundRawTransaction(tx, btcjson.FundRawTransactionOpts{ + FeeRate: &feeRate, + ChangePosition: &changePosition, + }, nil) if err != nil { return nil, err } - copiedTx := &wire.MsgTx{} - err = copier.Copy(copiedTx, tx) + + rl.logger.Debugf("Building a BTC tx using %s with data %x", rawTxResult.Transaction.TxID(), data) + + _, addresses, _, err := txscript.ExtractPkScriptAddrs( + rawTxResult.Transaction.TxOut[changePosition].PkScript, + rl.GetNetParams(), + ) + if err != nil { return nil, err } - txSize, err := calculateTxVirtualSize(copiedTx, utxo, changeScript) + + if len(addresses) == 0 { + return nil, errors.New("no change address found") + } + + changeAddr := addresses[0] + rl.logger.Debugf("Got a change address %v", changeAddr.String()) + + txSize, err := calculateTxVirtualSize(rawTxResult.Transaction) if err != nil { return nil, err } + + changeAmount := btcutil.Amount(rawTxResult.Transaction.TxOut[changePosition].Value) minRelayFee := rl.calcMinRelayFee(txSize) - if utxo.Amount < minRelayFee { - return nil, fmt.Errorf("the value of the utxo is not sufficient for relaying the tx. Require: %v. Have: %v", minRelayFee, utxo.Amount) + + if changeAmount < minRelayFee { + return nil, fmt.Errorf("the value of the utxo is not sufficient for relaying the tx. Require: %v. Have: %v", minRelayFee, changeAmount) } - txFee := rl.getFeeRate().FeeForVSize(txSize) + + txFee := rawTxResult.Fee // ensuring the tx fee is not lower than the minimum relay fee if txFee < minRelayFee { txFee = minRelayFee } // ensuring the tx fee is not higher than the utxo value - if utxo.Amount < txFee { - return nil, fmt.Errorf("the value of the utxo is not sufficient for paying the calculated fee of the tx. Calculated: %v. Have: %v", txFee, utxo.Amount) + if changeAmount < txFee { + return nil, fmt.Errorf("the value of the utxo is not sufficient for paying the calculated fee of the tx. Calculated: %v. Have: %v", txFee, changeAmount) } - change := utxo.Amount - txFee - tx.AddTxOut(wire.NewTxOut(int64(change), changeScript)) // sign tx - tx, err = rl.dumpPrivKeyAndSignTx(tx, utxo) + tx, err = rl.signTx(rawTxResult.Transaction) if err != nil { return nil, fmt.Errorf("failed to sign tx: %w", err) } // serialization var signedTxBytes bytes.Buffer - err = tx.Serialize(&signedTxBytes) - if err != nil { + if err := tx.Serialize(&signedTxBytes); err != nil { return nil, err } - rl.logger.Debugf("Successfully composed a BTC tx with balance of input: %v, "+ - "tx fee: %v, output value: %v, tx size: %v, hex: %v", - utxo.Amount, txFee, change, txSize, hex.EncodeToString(signedTxBytes.Bytes())) + change := changeAmount - txFee + + if change < dustThreshold { + return nil, fmt.Errorf("change amount is %v less then dust treshold %v", change, dustThreshold) + } + + rl.logger.Debugf("Successfully composed a BTC tx: tx fee: %v, output value: %v, tx size: %v, hex: %v", + txFee, changeAmount, txSize, hex.EncodeToString(signedTxBytes.Bytes())) return &types.BtcTxInfo{ Tx: tx, - Utxo: utxo, ChangeAddress: changeAddr, Size: txSize, Fee: txFee, @@ -490,10 +533,12 @@ func (rl *Relayer) getFeeRate() chainfee.SatPerKVByte { func (rl *Relayer) sendTxToBTC(tx *wire.MsgTx) (*chainhash.Hash, error) { rl.logger.Debugf("Sending tx %v to BTC", tx.TxHash().String()) + ha, err := rl.SendRawTransaction(tx, true) if err != nil { return nil, err } rl.logger.Debugf("Successfully sent tx %v to BTC", tx.TxHash().String()) + return ha, nil } diff --git a/submitter/relayer/utils.go b/submitter/relayer/utils.go index 7f267592..c909ac25 100644 --- a/submitter/relayer/utils.go +++ b/submitter/relayer/utils.go @@ -1,100 +1,16 @@ package relayer import ( - "bytes" "errors" - - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/mempool" - "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" - secp "github.com/decred/dcrd/dcrec/secp256k1/v4" - - "github.com/babylonlabs-io/vigilante/types" ) -func isSegWit(addr btcutil.Address) (bool, error) { - switch addr.(type) { - case *btcutil.AddressPubKeyHash, *btcutil.AddressScriptHash, *btcutil.AddressPubKey: - return false, nil - case *btcutil.AddressWitnessPubKeyHash, *btcutil.AddressWitnessScriptHash: - return true, nil - default: - return false, errors.New("non-supported address type") - } -} - -func calculateTxVirtualSize(tx *wire.MsgTx, utxo *types.UTXO, changeScript []byte) (int64, error) { - tx.AddTxOut(wire.NewTxOut(int64(utxo.Amount), changeScript)) - - // when calculating tx size we can use a random private key - privKey, err := secp.GeneratePrivateKey() - if err != nil { - return 0, err - } - - // add signature/witness depending on the type of the previous address - // if not segwit, add signature; otherwise, add witness - segwit, err := isSegWit(utxo.Addr) - if err != nil { - return 0, err - } - - tx, err = completeTxIn(tx, segwit, privKey, utxo) - if err != nil { - return 0, err - } - - var txBytes bytes.Buffer - err = tx.Serialize(&txBytes) - if err != nil { - return 0, err - } - btcTx, err := btcutil.NewTxFromBytes(txBytes.Bytes()) - if err != nil { - return 0, err - } - - return mempool.GetTxVirtualSize(btcTx), err -} - -func completeTxIn(tx *wire.MsgTx, isSegWit bool, privKey *btcec.PrivateKey, utxo *types.UTXO) (*wire.MsgTx, error) { - if !isSegWit { - sig, err := txscript.SignatureScript( - tx, - 0, - utxo.ScriptPK, - txscript.SigHashAll, - privKey, - true, - ) - if err != nil { - return nil, err - } - tx.TxIn[0].SignatureScript = sig - } else { - sighashes := txscript.NewTxSigHashes( - tx, - // Use the CannedPrevOutputFetcher which is only able to return information about a single UTXO - // See https://github.com/btcsuite/btcd/commit/e781b66e2fb9a354a14bfa7fbdd44038450cc13f - // for details on the output fetchers - txscript.NewCannedPrevOutputFetcher(utxo.ScriptPK, int64(utxo.Amount))) - wit, err := txscript.WitnessSignature( - tx, - sighashes, - 0, - int64(utxo.Amount), - utxo.ScriptPK, - txscript.SigHashAll, - privKey, - true, - ) - if err != nil { - return nil, err - } - tx.TxIn[0].Witness = wit +func calculateTxVirtualSize(tx *wire.MsgTx) (int64, error) { + if tx == nil { + return -1, errors.New("tx param nil") } - return tx, nil + return mempool.GetTxVirtualSize(btcutil.NewTx(tx)), 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/testutil/mocks/btcclient.go b/testutil/mocks/btcclient.go index 8023d652..76581b6c 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() @@ -236,19 +210,19 @@ func (m *MockBTCWallet) EXPECT() *MockBTCWalletMockRecorder { return m.recorder } -// DumpPrivKey mocks base method. -func (m *MockBTCWallet) DumpPrivKey(address btcutil.Address) (*btcutil.WIF, error) { +// FundRawTransaction mocks base method. +func (m *MockBTCWallet) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "DumpPrivKey", address) - ret0, _ := ret[0].(*btcutil.WIF) + ret := m.ctrl.Call(m, "FundRawTransaction", tx, opts, isWitness) + ret0, _ := ret[0].(*btcjson.FundRawTransactionResult) ret1, _ := ret[1].(error) return ret0, ret1 } -// DumpPrivKey indicates an expected call of DumpPrivKey. -func (mr *MockBTCWalletMockRecorder) DumpPrivKey(address interface{}) *gomock.Call { +// FundRawTransaction indicates an expected call of FundRawTransaction. +func (mr *MockBTCWalletMockRecorder) FundRawTransaction(tx, opts, isWitness interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DumpPrivKey", reflect.TypeOf((*MockBTCWallet)(nil).DumpPrivKey), address) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FundRawTransaction", reflect.TypeOf((*MockBTCWallet)(nil).FundRawTransaction), tx, opts, isWitness) } // GetBTCConfig mocks base method. @@ -383,6 +357,22 @@ func (mr *MockBTCWalletMockRecorder) SendRawTransaction(tx, allowHighFees interf return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawTransaction", reflect.TypeOf((*MockBTCWallet)(nil).SendRawTransaction), tx, allowHighFees) } +// SignRawTransactionWithWallet mocks base method. +func (m *MockBTCWallet) SignRawTransactionWithWallet(tx *wire.MsgTx) (*wire.MsgTx, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SignRawTransactionWithWallet", tx) + ret0, _ := ret[0].(*wire.MsgTx) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// SignRawTransactionWithWallet indicates an expected call of SignRawTransactionWithWallet. +func (mr *MockBTCWalletMockRecorder) SignRawTransactionWithWallet(tx interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SignRawTransactionWithWallet", reflect.TypeOf((*MockBTCWallet)(nil).SignRawTransactionWithWallet), tx) +} + // Stop mocks base method. func (m *MockBTCWallet) Stop() { m.ctrl.T.Helper() diff --git a/tools/go.mod b/tools/go.mod index 577a04bf..6f7f46dc 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -1,14 +1,8 @@ module github.com/babylonlabs-io/vigilante/tools -go 1.21 +go 1.23 -toolchain go1.21.4 - -require ( - github.com/babylonlabs-io/babylon v0.9.1 - github.com/btcsuite/btcd v0.24.2 - github.com/btcsuite/btcwallet v0.16.9 -) +require github.com/babylonlabs-io/babylon v0.9.1 require ( cloud.google.com/go v0.112.0 // indirect @@ -46,19 +40,11 @@ require ( github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d // indirect + github.com/btcsuite/btcd v0.24.2 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/btcsuite/btcd/btcutil v1.1.5 // indirect - github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // 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 - github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect - github.com/btcsuite/winsvc v1.0.0 // indirect github.com/cenkalti/backoff/v4 v4.2.0 // indirect github.com/cespare/xxhash v1.1.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -90,7 +76,6 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect - github.com/decred/dcrd/lru v1.0.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v2 v2.2007.4 // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect @@ -132,7 +117,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.3 // indirect + github.com/hashicorp/go-getter v1.7.5 // indirect github.com/hashicorp/go-hclog v1.5.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-metrics v0.5.3 // indirect @@ -147,24 +132,15 @@ require ( github.com/iancoleman/strcase v0.3.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jessevdk/go-flags v1.4.0 // indirect github.com/jinzhu/copier v0.3.5 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmhodges/levigo v1.0.0 // indirect - github.com/jrick/logrotate v1.0.0 // indirect github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect github.com/klauspost/compress v1.17.7 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.7 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect - 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.0 // indirect - github.com/lightningnetwork/lnd/clock v1.0.1 // indirect - github.com/lightningnetwork/lnd/queue v1.0.1 // indirect - github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect - github.com/lightningnetwork/lnd/tlv v1.0.2 // indirect github.com/linxGnu/grocksdb v1.8.14 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/manifoldco/promptui v0.9.0 // indirect diff --git a/tools/go.sum b/tools/go.sum index 6763dd5f..b6ffb2fb 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -288,24 +288,17 @@ github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d h1:zsO4lp+bjv5XvP github.com/boljen/go-bitmap v0.0.0-20151001105940-23cd2fb0ce7d/go.mod h1:f1iKL6ZhUWvbk7PdWVmOaak10o86cqMUYEmn1CZNGEI= github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= 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.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= -github.com/btcsuite/btcd/btcutil/psbt v1.1.8/go.mod h1:kA6FLH/JfUx++j9pYU0pyu+Z8XGBQuuTmuKYUf6q7/U= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= @@ -313,29 +306,12 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= -github.com/btcsuite/btcwallet v0.16.9 h1:hLAzEJvsiSn+r6j374G7ThnrYD/toa+Lv7l1Rm6+0oM= -github.com/btcsuite/btcwallet v0.16.9/go.mod h1:T3DjEAMZYIqQ28l+ixlB6DX4mFJXCX8Pzz+yACQcLsc= -github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 h1:etuLgGEojecsDOYTII8rYiGHjGyV5xTqsXi+ZQ715UU= -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= -github.com/btcsuite/btcwallet/walletdb v1.4.0 h1:/C5JRF+dTuE2CNMCO/or5N8epsrhmSM4710uBQoYPTQ= -github.com/btcsuite/btcwallet/walletdb v1.4.0/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU= -github.com/btcsuite/btcwallet/wtxmgr v1.5.0 h1:WO0KyN4l6H3JWnlFxfGR7r3gDnlGT7W2cL8vl6av4SU= -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/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= github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= -github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 h1:R8vQdOQdZ9Y3SkEwmHoWBmX1DNXhXZqlTpq6s4tyJGc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= -github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY= github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE= @@ -453,7 +429,6 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPc github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE= @@ -728,8 +703,8 @@ github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtng github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-getter v1.7.3 h1:bN2+Fw9XPFvOCjB0UOevFIMICZ7G2XSQHzfvLUyOM5E= -github.com/hashicorp/go-getter v1.7.3/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= +github.com/hashicorp/go-getter v1.7.5 h1:dT58k9hQ/vbxNMwoI5+xFYAJuv6152UNvdHokfI5wE4= +github.com/hashicorp/go-getter v1.7.5/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= @@ -786,7 +761,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/protoreflect v1.15.3 h1:6SFRuqU45u9hIZPJAoZ8c28T3nK64BNdp9w6jFonzls= github.com/jhump/protoreflect v1.15.3/go.mod h1:4ORHmSBmlCW8fh3xHmJMGyul1zNqZK4Elxc8qKP+p1k= @@ -801,7 +775,6 @@ github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -845,20 +818,6 @@ github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= -github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= -github.com/lightninglabs/neutrino v0.15.0 h1:yr3uz36fLAq8hyM0TRUVlef1TRNoWAqpmmNlVtKUDtI= -github.com/lightninglabs/neutrino v0.15.0/go.mod h1:pmjwElN/091TErtSE9Vd5W4hpxoG2/+xlb+HoPm9Gug= -github.com/lightninglabs/neutrino/cache v1.1.0 h1:szZIhVabiQIsGzJjhvo76sj05Au+zVotj2M34EquGME= -github.com/lightninglabs/neutrino/cache v1.1.0/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= -github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= -github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= -github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= -github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= -github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= -github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0= -github.com/lightningnetwork/lnd/tlv v1.0.2 h1:LG7H3Uw/mHYGnEeHRPg+STavAH+UsFvuBflD0PzcYFQ= -github.com/lightningnetwork/lnd/tlv v1.0.2/go.mod h1:fICAfsqk1IOsC1J7G9IdsWX1EqWRMqEDCNxZJSKr9C4= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ= @@ -1140,7 +1099,6 @@ github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw= github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.8 h1:xs88BrvEv273UsB79e0hcVrlUWmS0a8upikMFhSyAtA= go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= diff --git a/tools/tools.go b/tools/tools.go index 54f759ac..01550773 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -5,6 +5,4 @@ package vigilante import ( _ "github.com/babylonlabs-io/babylon/cmd/babylond" - _ "github.com/btcsuite/btcd" - _ "github.com/btcsuite/btcwallet" ) diff --git a/types/ckpt_info.go b/types/ckpt_info.go index 4c9b93fc..5d64c6b0 100644 --- a/types/ckpt_info.go +++ b/types/ckpt_info.go @@ -21,7 +21,6 @@ type BtcTxInfo struct { TxId *chainhash.Hash Tx *wire.MsgTx ChangeAddress btcutil.Address - Utxo *UTXO // the UTXO used to build this BTC tx Size int64 // the size of the BTC tx Fee btcutil.Amount // tx fee cost by the BTC tx } diff --git a/types/epoch_info.go b/types/epoch_info.go index b4d74559..7e2a6121 100644 --- a/types/epoch_info.go +++ b/types/epoch_info.go @@ -2,8 +2,6 @@ package types import ( "bytes" - "fmt" - "github.com/babylonlabs-io/babylon/crypto/bls12381" ckpttypes "github.com/babylonlabs-io/babylon/x/checkpointing/types" "github.com/boljen/go-bitmap" @@ -65,10 +63,10 @@ func (ei *EpochInfo) Equal(epochInfo *EpochInfo) bool { func (ei *EpochInfo) VerifyMultiSig(ckpt *ckpttypes.RawCheckpoint) error { signerKeySet, sumPower, err := ei.GetSignersKeySetWithPowerSum(ckpt.Bitmap) if sumPower*3 <= ei.GetTotalPower()*2 { - return errors.Wrapf(ErrInsufficientPower, fmt.Sprintf("expected to be greater than %v, got %v", ei.GetTotalPower()*2/3, sumPower)) + return errors.Wrapf(ErrInsufficientPower, "expected to be greater than %v, got %v", ei.GetTotalPower()*2/3, sumPower) } if err != nil { - return errors.Wrapf(ErrInvalidMultiSig, fmt.Sprintf("failed to get signer set: %s", err.Error())) + return errors.Wrapf(ErrInvalidMultiSig, "failed to get signer set: %s", err.Error()) } msgBytes := ckpt.SignedMsg() valid, err := bls12381.VerifyMultiSig(*ckpt.BlsMultiSig, signerKeySet, msgBytes) 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 -} 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 -}