Skip to content
This repository has been archived by the owner on Apr 16, 2024. It is now read-only.

feat: vanilla implementation #1

Merged
merged 3 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Use the latest 2.1 version of CircleCI pipeline process engine.
# See: https://circleci.com/docs/2.0/configuration-reference
version: 2.1

# Define a job to be invoked later in a workflow.
# See: https://circleci.com/docs/2.0/configuration-reference/#jobs
jobs:
build:
# Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.
# See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor
machine:
image: ubuntu-2204:2022.07.1
resource_class: large
# Add steps to the job
# See: https://circleci.com/docs/2.0/configuration-reference/#steps
steps:
- checkout
- run:
name: Print Go environment
command: "go env"
- restore_cache: # restores saved cache if no changes are detected since last run
keys:
- go-mod-v6-{{ checksum "go.sum" }}
- run:
name: Install Dependencies
command: sudo apt-get install libzmq3-dev
- run:
name: Build vigilante
command: make build
- save_cache:
key: go-mod-v6-{{ checksum "go.sum" }}
paths:
- "/home/circleci/.go_workspace/pkg/mod"
- run:
name: Run tests
command: |
make test

# Invoke jobs via workflows
# See: https://circleci.com/docs/2.0/configuration-reference/#workflows
workflows:
build-and-test:
jobs:
- build
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@

# Dependency directories (remove the comment below to include it)
# vendor/
.vscode/
.idea/
.testnet/
.DS_Store
main
tmp/
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
MOCKS_DIR=$(CURDIR)/testutil/mocks/
MOCKGEN_REPO=github.com/golang/mock/mockgen
MOCKGEN_VERSION=v1.6.0
MOCKGEN_CMD=go run ${MOCKGEN_REPO}@${MOCKGEN_VERSION}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rules for building would also be useful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, is there a usecase in which someone uses this client directly from this repository without anything else? Currently, I do not think we have such a usecase so a build rule might not be needed.

In the future, I can envision this client also providing a CLI interface in which someone can interact with a remote Babylon node (although the same can be accomplished through babylond commands so this would be a wrapper).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. We should implement it as a command line tool just like lens did.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good! An issue for that would be appreciated

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

test:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this depend on the mocks getting generated by mock-gen? Not entirely familiar with the mockgen package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying we should re-generate mock files every time before we run tests? Not sure about that. It seems we can manually generate mock files once and upload them in the repo so that others do not need to generate them again, just like the protobuf files.

go test ./...

mock-gen:
mkdir -p $(MOCKS_DIR)
$(MOCKGEN_CMD) -source=client/interface.go -package mocks -destination $(MOCKS_DIR)/client.go
55 changes: 55 additions & 0 deletions client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package client

import (
"fmt"
"time"

"github.com/babylonchain/rpc-client/config"
lensclient "github.com/strangelove-ventures/lens/client"
"go.uber.org/zap"
)

var _ BabylonClient = &Client{}

type Client struct {
*lensclient.ChainClient
cfg *config.BabylonConfig

log *zap.Logger

// retry attributes
retrySleepTime time.Duration
maxRetrySleepTime time.Duration
}

func New(cfg *config.BabylonConfig, log *zap.Logger, retrySleepTime, maxRetrySleepTime time.Duration) (*Client, error) {
// create a Tendermint/Cosmos client for Babylon
cc, err := newLensClient(cfg.Unwrap())
if err != nil {
return nil, err
}

// wrap to our type
client := &Client{cc, cfg, log, retrySleepTime, maxRetrySleepTime}

return client, nil
}

func (c *Client) GetConfig() *config.BabylonConfig {
return c.cfg
}

func (c *Client) GetTagIdx() uint8 {
tagIdxStr := c.cfg.TagIdx
if len(tagIdxStr) != 1 {
panic(fmt.Errorf("tag index should be one byte"))
}
// convert tagIdx from string to its ascii value
return uint8(rune(tagIdxStr[0]))
}

func (c *Client) Stop() {
if c.RPCClient != nil && c.RPCClient.IsRunning() {
<-c.RPCClient.Quit()
}
}
45 changes: 45 additions & 0 deletions client/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package client

import (
btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types"
btclctypes "github.com/babylonchain/babylon/x/btclightclient/types"
checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types"
epochingtypes "github.com/babylonchain/babylon/x/epoching/types"
"github.com/babylonchain/rpc-client/config"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

type BabylonClient interface {
Stop()
GetConfig() *config.BabylonConfig
GetTagIdx() uint8
GetAddr() (sdk.AccAddress, error)
MustGetAddr() sdk.AccAddress
InsertBTCSpvProof(msg *btcctypes.MsgInsertBTCSpvProof) (*sdk.TxResponse, error)
InsertHeader(msg *btclctypes.MsgInsertHeader) (*sdk.TxResponse, error)
InsertHeaders(msgs []*btclctypes.MsgInsertHeader) (*sdk.TxResponse, error)
MustInsertBTCSpvProof(msg *btcctypes.MsgInsertBTCSpvProof) *sdk.TxResponse

// staking module related queries
QueryStakingParams() (*stakingtypes.Params, error)

// epoch module related queries
QueryEpochingParams() (*epochingtypes.Params, error)

// btclightclient module related queries
QueryBTCLightclientParams() (*btclctypes.Params, error)
QueryHeaderChainTip() (*chainhash.Hash, uint64, error)
QueryBaseHeader() (*wire.BlockHeader, uint64, error)
QueryContainsBlock(blockHash *chainhash.Hash) (bool, error)

// btccheckpoint module related queries
QueryBTCCheckpointParams() (*btcctypes.Params, error)
MustQueryBTCCheckpointParams() *btcctypes.Params

// checkpointing module related queries
QueryRawCheckpoint(epochNumber uint64) (*checkpointingtypes.RawCheckpointWithMeta, error)
QueryRawCheckpointList(status checkpointingtypes.CheckpointStatus) ([]*checkpointingtypes.RawCheckpointWithMeta, error)
}
19 changes: 19 additions & 0 deletions client/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package client

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func (c *Client) GetAddr() (sdk.AccAddress, error) {
return c.ChainClient.GetKeyAddress()
}

func (c *Client) MustGetAddr() sdk.AccAddress {
addr, err := c.ChainClient.GetKeyAddress()
if err != nil {
panic(fmt.Errorf("Failed to get signer: %v", err))
}
return addr
}
60 changes: 60 additions & 0 deletions client/keys_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package client_test

import (
"go.uber.org/zap/zaptest"
"math/rand"
"strings"
"testing"
"time"

bbn "github.com/babylonchain/babylon/app"
"github.com/babylonchain/babylon/testutil/datagen"
"github.com/babylonchain/rpc-client/client"
"github.com/babylonchain/rpc-client/config"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/stretchr/testify/require"
)

func FuzzKeys(f *testing.F) {
datagen.AddRandomSeedsToFuzzer(f, 10)

f.Fuzz(func(t *testing.T, seed int64) {
rand.Seed(seed)

// create a keyring
keyringName := datagen.GenRandomHexStr(10)
dir := t.TempDir()
mockIn := strings.NewReader("")
cdc := bbn.MakeTestEncodingConfig()
kr, err := keyring.New(keyringName, "test", dir, mockIn, cdc.Marshaler)
require.NoError(t, err)

// create a random key pair in this keyring
keyName := datagen.GenRandomHexStr(10)
kr.NewMnemonic(
keyName,
keyring.English,
hd.CreateHDPath(118, 0, 0).String(),
keyring.DefaultBIP39Passphrase,
hd.Secp256k1,
)

// create a Babylon client with this random keyring
cfg := config.DefaultBabylonConfig()
cfg.KeyDirectory = dir
cfg.Key = keyName
cl, err := client.New(&cfg, zaptest.NewLogger(t), 1*time.Minute, 5*time.Minute)
require.NoError(t, err)

// retrieve the key info from key ring
keys, err := kr.List()
require.NoError(t, err)
require.Equal(t, 1, len(keys))

// test if the key is consistent in Babylon client and keyring
bbnAddr := cl.MustGetAddr()
addr, _ := keys[0].GetAddress()
require.Equal(t, addr, bbnAddr)
})
}
30 changes: 30 additions & 0 deletions client/modified_lensclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package client

import (
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
lensclient "github.com/strangelove-ventures/lens/client"
)

// adapted from https://github.com/strangelove-ventures/lens/blob/v0.5.1/client/chain_client.go#L48-L63
// The notable difference is parsing the key directory
// TODO: key directory support for different types of keyring backend
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a Github issue for that for easier tracking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SebastianElvis Not sure about this description. Could you please revisit this TODO and create an issue for tracking if it is needed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, created #2

func newLensClient(ccc *lensclient.ChainClientConfig, kro ...keyring.Option) (*lensclient.ChainClient, error) {
// attach the supported algorithms to the keyring options
keyringOptions := []keyring.Option{}
keyringOptions = append(keyringOptions, func(options *keyring.Options) {
options.SupportedAlgos = keyring.SigningAlgoList{hd.Secp256k1}
options.SupportedAlgosLedger = keyring.SigningAlgoList{hd.Secp256k1}
})
keyringOptions = append(keyringOptions, kro...)

cc := &lensclient.ChainClient{
KeyringOptions: keyringOptions,
Config: ccc,
Codec: lensclient.MakeCodec(ccc.Modules),
}
if err := cc.Init(); err != nil {
return nil, err
}
return cc, nil
}
38 changes: 38 additions & 0 deletions client/query_btc_checkpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package client

import (
"github.com/babylonchain/babylon/types/retry"
btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types"
"github.com/strangelove-ventures/lens/client/query"
)

// QueryBTCCheckpointParams queries btccheckpoint module's parameters via ChainClient
func (c *Client) QueryBTCCheckpointParams() (*btcctypes.Params, error) {
query := query.Query{Client: c.ChainClient, Options: query.DefaultOptions()}
ctx, cancel := query.GetQueryContext()
defer cancel()

queryClient := btcctypes.NewQueryClient(c.ChainClient)
req := &btcctypes.QueryParamsRequest{}
resp, err := queryClient.Params(ctx, req)
if err != nil {
return &btcctypes.Params{}, err
}
return &resp.Params, nil
}

func (c *Client) MustQueryBTCCheckpointParams() *btcctypes.Params {
var (
params *btcctypes.Params
err error
)

err = retry.Do(c.retrySleepTime, c.maxRetrySleepTime, func() error {
params, err = c.QueryBTCCheckpointParams()
return err
})
if err != nil {
panic(err)
}
return params
}
73 changes: 73 additions & 0 deletions client/query_btc_lightclient.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package client

import (
btclctypes "github.com/babylonchain/babylon/x/btclightclient/types"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/strangelove-ventures/lens/client/query"
)

// QueryBTCLightclientParams queries btclightclient module's parameters via ChainClient
func (c *Client) QueryBTCLightclientParams() (*btclctypes.Params, error) {
query := query.Query{Client: c.ChainClient, Options: query.DefaultOptions()}
ctx, cancel := query.GetQueryContext()
defer cancel()

queryClient := btclctypes.NewQueryClient(c.ChainClient)
req := &btclctypes.QueryParamsRequest{}
resp, err := queryClient.Params(ctx, req)
if err != nil {
return &btclctypes.Params{}, err
}
return &resp.Params, nil
}

// QueryHeaderChainTip queries hash/height of the latest BTC block in the btclightclient module
func (c *Client) QueryHeaderChainTip() (*chainhash.Hash, uint64, error) {
query := query.Query{Client: c.ChainClient, Options: query.DefaultOptions()}
ctx, cancel := query.GetQueryContext()
defer cancel()

queryClient := btclctypes.NewQueryClient(c.ChainClient)
req := &btclctypes.QueryTipRequest{}
resp, err := queryClient.Tip(ctx, req)
if err != nil {
return nil, 0, err
}

return resp.Header.Hash.ToChainhash(), resp.Header.Height, nil
}

func (c *Client) QueryBaseHeader() (*wire.BlockHeader, uint64, error) {
query := query.Query{Client: c.ChainClient, Options: query.DefaultOptions()}
ctx, cancel := query.GetQueryContext()
defer cancel()

queryClient := btclctypes.NewQueryClient(c.ChainClient)

req := &btclctypes.QueryBaseHeaderRequest{}
resp, err := queryClient.BaseHeader(ctx, req)
if err != nil {
return nil, 0, err
}

header := resp.Header.Header.ToBlockHeader()
height := resp.Header.Height

return header, height, nil
}

func (c *Client) QueryContainsBlock(blockHash *chainhash.Hash) (bool, error) {
query := query.Query{Client: c.ChainClient, Options: query.DefaultOptions()}
ctx, cancel := query.GetQueryContext()
defer cancel()

queryClient := btclctypes.NewQueryClient(c.ChainClient)
req := btclctypes.QueryContainsBytesRequest{Hash: blockHash.CloneBytes()}
resp, err := queryClient.ContainsBytes(ctx, &req)
if err != nil {
return false, err
}

return resp.Contains, nil
}
Loading