Skip to content

Commit

Permalink
Release v0.2.0 (#118)
Browse files Browse the repository at this point in the history
  • Loading branch information
gitferry authored Jun 7, 2024
2 parents 29185f6 + cc9a679 commit bb93c71
Show file tree
Hide file tree
Showing 28 changed files with 570 additions and 879 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
resource_class: large
steps:
- go/install:
version: "1.21.6"
version: "1.22.3"
- checkout
- run:
name: Print Go environment
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.21.6-alpine as builder
FROM golang:1.22.3-alpine as builder

# TARGETPLATFORM should be one of linux/amd64 or linux/arm64.
ARG TARGETPLATFORM="linux/amd64"
Expand Down
39 changes: 32 additions & 7 deletions btcclient/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,25 @@ func (c *BTCClient) GetTipHeight() (uint64, error) {
}

func (c *BTCClient) GetBlockByHeight(height uint64) (*types.IndexedBlock, error) {
blockHash, err := c.GetBlockHashByHeight(height)
if err != nil {
return nil, err
}

callForBlock := func() (*wire.MsgBlock, error) {
return c.client.GetBlock(blockHash)
}

block, err := clientCallWithRetry(callForBlock, c.logger, c.cfg)
if err != nil {
return nil, fmt.Errorf("failed to get block by hash %s: %w", blockHash.String(), err)
}

btcTxs := utils.GetWrappedTxs(block)
return types.NewIndexedBlock(int32(height), &block.Header, btcTxs), nil
}

func (c *BTCClient) GetBlockHashByHeight(height uint64) (*chainhash.Hash, error) {
callForBlockHash := func() (*chainhash.Hash, error) {
return c.client.GetBlockHash(int64(height))
}
Expand All @@ -65,19 +84,25 @@ func (c *BTCClient) GetBlockByHeight(height uint64) (*types.IndexedBlock, error)
return nil, fmt.Errorf("failed to get block by height %d: %w", height, err)
}

callForBlock := func() (*wire.MsgBlock, error) {
return c.client.GetBlock(blockHash)
return blockHash, nil
}

func (c *BTCClient) GetBlockHeaderByHeight(height uint64) (*wire.BlockHeader, error) {
blockHash, err := c.GetBlockHashByHeight(height)
if err != nil {
return nil, err
}

block, err := clientCallWithRetry(callForBlock, c.logger, c.cfg)
callForBlockHeader := func() (*wire.BlockHeader, error) {
return c.client.GetBlockHeader(blockHash)
}

header, err := clientCallWithRetry(callForBlockHeader, c.logger, c.cfg)
if err != nil {
return nil, fmt.Errorf("failed to get block by hash %s: %w", blockHash.String(), err)
return nil, fmt.Errorf("failed to get block header by hash %s: %w", blockHash.String(), err)
}

btcTxs := utils.GetWrappedTxs(block)

return types.NewIndexedBlock(int32(height), &block.Header, btcTxs), nil
return header, nil
}

func clientCallWithRetry[T any](
Expand Down
7 changes: 1 addition & 6 deletions btcscanner/block_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,8 @@ func (bs *BtcPoller) handleNewBlock(blockEpoch *notifier.BlockEpoch) error {
return fmt.Errorf("failed to add the block %d to cache: %w", ib.Height, err)
}

params, err := bs.paramsVersions.GetParamsForBTCHeight(blockEpoch.Height)
if err != nil {
return fmt.Errorf("failed to get parameters for height %d: %w", blockEpoch.Height, err)
}

// try to extract confirmed blocks
confirmedBlocks := bs.unconfirmedBlockCache.TrimConfirmedBlocks(int(params.ConfirmationDepth) - 1)
confirmedBlocks := bs.unconfirmedBlockCache.TrimConfirmedBlocks(int(bs.confirmationDepth) - 1)

bs.commitChainUpdate(confirmedBlocks)

Expand Down
29 changes: 9 additions & 20 deletions btcscanner/btc_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
var _ BtcScanner = (*BtcPoller)(nil)

type BtcScanner interface {
Start(startHeight uint64) error
Start(startHeight, activationHeight uint64) error

// ChainUpdateInfoChan receives the chain update info
// after bootstrapping or when new block is received
Expand Down Expand Up @@ -44,7 +44,7 @@ type BtcPoller struct {
btcClient Client
btcNotifier notifier.ChainNotifier

paramsVersions *types.ParamsVersions
confirmationDepth uint16

// the current tip BTC block
confirmedTipBlock *types.IndexedBlock
Expand All @@ -62,7 +62,7 @@ type BtcPoller struct {
}

func NewBTCScanner(
paramsVersions *types.ParamsVersions,
confirmationDepth uint16,
logger *zap.Logger,
btcClient Client,
btcNotifier notifier.ChainNotifier,
Expand All @@ -76,7 +76,7 @@ func NewBTCScanner(
logger: logger.With(zap.String("module", "btcscanner")),
btcClient: btcClient,
btcNotifier: btcNotifier,
paramsVersions: paramsVersions,
confirmationDepth: confirmationDepth,
chainUpdateInfoChan: make(chan *ChainUpdateInfo),
unconfirmedBlockCache: unconfirmedBlockCache,
isSynced: atomic.NewBool(false),
Expand All @@ -86,12 +86,12 @@ func NewBTCScanner(
}

// Start starts the scanning process from the last confirmed height + 1
func (bs *BtcPoller) Start(startHeight uint64) error {
func (bs *BtcPoller) Start(startHeight, activationHeight uint64) error {
if bs.isStarted.Swap(true) {
return fmt.Errorf("the BTC scanner is already started")
}

if err := bs.waitUntilActivation(); err != nil {
if err := bs.waitUntilActivation(activationHeight); err != nil {
return err
}

Expand All @@ -110,9 +110,7 @@ func (bs *BtcPoller) Start(startHeight uint64) error {
return nil
}

func (bs *BtcPoller) waitUntilActivation() error {
activationHeight := bs.paramsVersions.ParamsVersions[0].ActivationHeight

func (bs *BtcPoller) waitUntilActivation(activationHeight uint64) error {
for {
tipHeight, err := bs.btcClient.GetTipHeight()
if err != nil {
Expand Down Expand Up @@ -154,11 +152,6 @@ func (bs *BtcPoller) Bootstrap(startHeight uint64) error {
return fmt.Errorf("the start height %d is higher than the current tip height %d", startHeight, tipHeight)
}

params, err := bs.paramsVersions.GetParamsForBTCHeight(int32(tipHeight))
if err != nil {
return fmt.Errorf("cannot get the global parameters for height %d", tipHeight)
}

var confirmedBlocks []*types.IndexedBlock
for i := startHeight; i <= tipHeight; i++ {
ib, err := bs.btcClient.GetBlockByHeight(i)
Expand All @@ -179,7 +172,7 @@ func (bs *BtcPoller) Bootstrap(startHeight uint64) error {
return fmt.Errorf("failed to add the block %d to cache: %w", ib.Height, err)
}

tempConfirmedBlocks := bs.unconfirmedBlockCache.TrimConfirmedBlocks(int(params.ConfirmationDepth) - 1)
tempConfirmedBlocks := bs.unconfirmedBlockCache.TrimConfirmedBlocks(int(bs.confirmationDepth) - 1)
confirmedBlocks = append(confirmedBlocks, tempConfirmedBlocks...)
}

Expand All @@ -199,12 +192,8 @@ func (bs *BtcPoller) GetUnconfirmedBlocks() ([]*types.IndexedBlock, error) {
if tipBlock == nil {
return nil, nil
}
params, err := bs.paramsVersions.GetParamsForBTCHeight(tipBlock.Height)
if err != nil {
return nil, fmt.Errorf("failed to get params for height %d: %w", tipBlock.Height, err)
}

lastBlocks := bs.unconfirmedBlockCache.GetLastBlocks(int(params.ConfirmationDepth) - 1)
lastBlocks := bs.unconfirmedBlockCache.GetLastBlocks(int(bs.confirmationDepth) - 1)

return lastBlocks, nil
}
Expand Down
8 changes: 4 additions & 4 deletions btcscanner/btc_scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ func FuzzPoller(f *testing.F) {
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
versionedParams := datagen.GenerateGlobalParamsVersions(r, t)
k := uint64(versionedParams.ParamsVersions[0].ConfirmationDepth)
startHeight := versionedParams.ParamsVersions[0].ActivationHeight
k := uint64(versionedParams.Versions[0].ConfirmationDepth)
startHeight := versionedParams.Versions[0].ActivationHeight
// Generate a random number of blocks
numBlocks := bbndatagen.RandomIntOtherThan(r, 0, 50) + k // make sure we have at least k+1 entry
chainIndexedBlocks := datagen.GetRandomIndexedBlocks(r, startHeight, numBlocks)
Expand All @@ -38,7 +38,7 @@ func FuzzPoller(f *testing.F) {
Return(chainIndexedBlocks[i], nil).AnyTimes()
}

btcScanner, err := btcscanner.NewBTCScanner(versionedParams, zap.NewNop(), mockBtcClient, &mock.ChainNotifier{})
btcScanner, err := btcscanner.NewBTCScanner(uint16(k), zap.NewNop(), mockBtcClient, &mock.ChainNotifier{})
require.NoError(t, err)

var wg sync.WaitGroup
Expand All @@ -54,7 +54,7 @@ func FuzzPoller(f *testing.F) {
require.Equal(t, bestHeight, updateInfo.TipUnconfirmedBlock.Height)
}()

err = btcScanner.Start(startHeight)
err = btcScanner.Start(startHeight, startHeight)
require.NoError(t, err)
defer func() {
err := btcScanner.Stop()
Expand Down
2 changes: 2 additions & 0 deletions btcscanner/expected_btc_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package btcscanner

import (
"github.com/babylonchain/staking-indexer/types"
"github.com/btcsuite/btcd/wire"
)

type Client interface {
GetTipHeight() (uint64, error)
GetBlockByHeight(height uint64) (*types.IndexedBlock, error)
GetBlockHeaderByHeight(height uint64) (*wire.BlockHeader, error)
}
141 changes: 141 additions & 0 deletions cmd/sid/cli/btc_headers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package cli

import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"

babylontypes "github.com/babylonchain/babylon/types"
bbnbtclightclienttypes "github.com/babylonchain/babylon/x/btclightclient/types"
"github.com/babylonchain/staking-indexer/btcclient"
"github.com/babylonchain/staking-indexer/btcscanner"
"github.com/babylonchain/staking-indexer/config"
"github.com/babylonchain/staking-indexer/log"
"github.com/babylonchain/staking-indexer/utils"
"github.com/urfave/cli"
"go.uber.org/zap"

sdkmath "cosmossdk.io/math"
)

const (
outputFileFlag = "output"
defaultOutputFileName = "btc-headers.json"
)

var BtcHeaderCommand = cli.Command{
Name: "btc-headers",
Usage: "Output a range of BTC headers into a JSON file.",
Description: "Output a range of BTC headers into a JSON file.",
UsageText: fmt.Sprintf("btc-headers [from] [to] [--%s=path/to/btc-headers.json]", outputFileFlag),
Flags: []cli.Flag{
cli.StringFlag{
Name: homeFlag,
Usage: "The path to the staking indexer home directory",
Value: config.DefaultHomeDir,
},
cli.StringFlag{
Name: outputFileFlag,
Usage: "The path to the output file",
Value: filepath.Join(config.DefaultHomeDir, defaultOutputFileName),
},
},
Action: btcHeaders,
}

func btcHeaders(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 2 {
return fmt.Errorf("not enough params, please specify [from] and [to]")
}

fromStr, toStr := args[0], args[1]
fromBlock, err := strconv.ParseUint(fromStr, 10, 64)
if err != nil {
return fmt.Errorf("unable to parse %s: %w", fromStr, err)
}

toBlock, err := strconv.ParseUint(toStr, 10, 64)
if err != nil {
return fmt.Errorf("unable to parse %s: %w", toStr, err)
}

if fromBlock > toBlock {
return fmt.Errorf("the [from] %d should not be greater than the [to] %d", fromBlock, toBlock)
}

homePath, err := filepath.Abs(ctx.String(homeFlag))
if err != nil {
return err
}
homePath = utils.CleanAndExpandPath(homePath)

cfg, err := config.LoadConfig(homePath)
if err != nil {
return fmt.Errorf("failed to load configuration: %w", err)
}

logger, err := log.NewRootLoggerWithFile(config.LogFile(homePath), cfg.LogLevel)
if err != nil {
return fmt.Errorf("failed to initialize the logger: %w", err)
}

btcClient, err := btcclient.NewBTCClient(
cfg.BTCConfig,
logger,
)
if err != nil {
return fmt.Errorf("failed to initialize the BTC client: %w", err)
}

btcHeaders, err := BtcHeaderInfoList(btcClient, fromBlock, toBlock)
if err != nil {
return fmt.Errorf("failed to get BTC headers: %w", err)
}

genState := bbnbtclightclienttypes.GenesisState{
BtcHeaders: btcHeaders,
}

bz, err := json.MarshalIndent(genState, "", " ")
if err != nil {
return fmt.Errorf("failed to generate json to set to output file %+v: %w", genState, err)
}

outputFilePath := ctx.String(outputFileFlag)
if err := os.WriteFile(outputFilePath, bz, 0644); err != nil {
return fmt.Errorf("failed to write to output file %s: %w", outputFilePath, err)
}

logger.Info(
"Successfully wrote btc headers to file",
zap.Uint64("fromBlock", fromBlock),
zap.Uint64("toBlock", toBlock),
zap.String("outputFile", outputFilePath),
)
return nil
}

// BtcHeaderInfoList queries the btc client for (fromBlk ~ toBlk) BTC blocks, converting to BTCHeaderInfo.
func BtcHeaderInfoList(btcClient btcscanner.Client, fromBlk, toBlk uint64) ([]*bbnbtclightclienttypes.BTCHeaderInfo, error) {
btcHeaders := make([]*bbnbtclightclienttypes.BTCHeaderInfo, 0, toBlk-fromBlk+1)
var currenWork = sdkmath.ZeroUint()

for blkHeight := fromBlk; blkHeight <= toBlk; blkHeight++ {
blkHeader, err := btcClient.GetBlockHeaderByHeight(blkHeight)
if err != nil {
return nil, fmt.Errorf("failed to get block height %d from BTC client: %w", blkHeight, err)
}

headerWork := bbnbtclightclienttypes.CalcHeaderWork(blkHeader)
currenWork = bbnbtclightclienttypes.CumulativeWork(headerWork, currenWork)

headerBytes := babylontypes.NewBTCHeaderBytesFromBlockHeader(blkHeader)

bbnBtcHeaderInfo := bbnbtclightclienttypes.NewBTCHeaderInfo(&headerBytes, headerBytes.Hash(), blkHeight, &currenWork)
btcHeaders = append(btcHeaders, bbnBtcHeaderInfo)
}
return btcHeaders, nil
}
Loading

0 comments on commit bb93c71

Please sign in to comment.