Skip to content

Commit

Permalink
feat: btclightclient: Abstract the usage of btcd types and store BTCH…
Browse files Browse the repository at this point in the history
…eaderInfo objects (#58)
  • Loading branch information
vitsalis authored Jul 11, 2022
1 parent e625af4 commit fd02a3e
Show file tree
Hide file tree
Showing 30 changed files with 538 additions and 591 deletions.
12 changes: 3 additions & 9 deletions proto/babylon/btclightclient/btclightclient.proto
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,6 @@ import "gogoproto/gogo.proto";

option go_package = "github.com/babylonchain/babylon/x/btclightclient/types";

// BaseBTCHeader corresponds to the oldest BTC header maintained in storage
// It is denoted by the header bytes and the height
message BaseBTCHeader {
bytes header = 1 [
(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderBytes"
];
uint64 height = 2;
}

message BTCHeaderInfo {
bytes header = 1 [
(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderBytes"
Expand All @@ -22,5 +13,8 @@ message BTCHeaderInfo {
(gogoproto.customtype) = "github.com/babylonchain/babylon/types.BTCHeaderHashBytes"
];
uint64 height = 3;
bytes work = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint"
];
}

2 changes: 1 addition & 1 deletion proto/babylon/btclightclient/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ option go_package = "github.com/babylonchain/babylon/x/btclightclient/types";
message GenesisState {
Params params = 1 [(gogoproto.nullable) = false];

BaseBTCHeader base_btc_header = 2 [(gogoproto.nullable) = false];
BTCHeaderInfo base_btc_header = 2 [(gogoproto.nullable) = false];
}
27 changes: 27 additions & 0 deletions types/btc_header_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,33 @@ func (m *BTCHeaderBytes) FromBlockHeader(header *wire.BlockHeader) {
}
}

func (m *BTCHeaderBytes) HasParent(header *BTCHeaderBytes) bool {
current := m.ToBlockHeader()
parent := header.ToBlockHeader()

return current.PrevBlock.String() == parent.BlockHash().String()
}

func (m *BTCHeaderBytes) Eq(other *BTCHeaderBytes) bool {
return m.Hash().Eq(other.Hash())
}

func (m *BTCHeaderBytes) Hash() *BTCHeaderHashBytes {
blockHash := m.ToBlockHeader().BlockHash()
hashBytes := NewBTCHeaderHashBytesFromChainhash(&blockHash)
return &hashBytes
}

func (m *BTCHeaderBytes) ParentHash() *BTCHeaderHashBytes {
parentHash := m.ToBlockHeader().PrevBlock
hashBytes := NewBTCHeaderHashBytesFromChainhash(&parentHash)
return &hashBytes
}

func (m *BTCHeaderBytes) Bits() uint32 {
return m.ToBlockHeader().Bits
}

func toBlockHeader(data []byte) (*wire.BlockHeader, error) {
// Create an empty header
header := &wire.BlockHeader{}
Expand Down
56 changes: 56 additions & 0 deletions types/btc_header_bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,3 +202,59 @@ func FuzzBTCHeaderBytesBtcdBlockOps(f *testing.F) {
}
})
}

func FuzzBTCHeaderBytesOperators(f *testing.F) {
defaultHeader, _ := types.NewBTCHeaderBytesFromHex("00006020c6c5a20e29da938a252c945411eba594cbeba021a1e20000000000000000000039e4bd0cd0b5232bb380a9576fcfe7d8fb043523f7a158187d9473e44c1740e6b4fa7c62ba01091789c24c22")
defaultBtcdHeader := defaultHeader.ToBlockHeader()

f.Add(
defaultBtcdHeader.Version,
defaultBtcdHeader.Bits,
defaultBtcdHeader.Nonce,
defaultBtcdHeader.Timestamp.Unix(),
defaultBtcdHeader.PrevBlock.String(),
defaultBtcdHeader.MerkleRoot.String(),
int64(17))

f.Fuzz(func(t *testing.T, version int32, bits uint32, nonce uint32,
timeInt int64, prevBlockStr string, merkleRootStr string, seed int64) {

rand.Seed(seed)
btcdHeader := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, prevBlockStr, merkleRootStr)

btcdHeaderHash := btcdHeader.BlockHash()
childPrevBlock := types.NewBTCHeaderHashBytesFromChainhash(&btcdHeaderHash)
btcdHeaderChild := datagen.GenRandomBtcdHeader(version, bits, nonce, timeInt, childPrevBlock.MarshalHex(), merkleRootStr)

var hb, hb2, hbChild types.BTCHeaderBytes
hb.FromBlockHeader(btcdHeader)
hb2.FromBlockHeader(btcdHeader)
hbChild.FromBlockHeader(btcdHeaderChild)

if !hb.Eq(&hb) {
t.Errorf("BTCHeaderBytes object does not equal itself")
}
if !hb.Eq(&hb2) {
t.Errorf("BTCHeaderBytes object does not equal a different object with the same bytes")
}
if hb.Eq(&hbChild) {
t.Errorf("BTCHeaderBytes object equals a different object with different bytes")
}

if !hbChild.HasParent(&hb) {
t.Errorf("HasParent method returns false with a correct parent")
}
if hbChild.HasParent(&hbChild) {
t.Errorf("HasParent method returns true for the same object")
}

if !hbChild.ParentHash().Eq(&childPrevBlock) {
t.Errorf("ParentHash did not return the parent hash")
}

if !hb.Hash().Eq(&childPrevBlock) {
t.Errorf("Hash method does not return the correct hash")
}

})
}
9 changes: 9 additions & 0 deletions types/btc_header_hash_bytes.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ func (m *BTCHeaderHashBytes) FromChainhash(hash *chainhash.Hash) {
}
}

func (m *BTCHeaderHashBytes) String() string {
return m.ToChainhash().String()
}

func (m *BTCHeaderHashBytes) Eq(hash *BTCHeaderHashBytes) bool {
// TODO: test
return m.String() == hash.String()
}

func toChainhash(data []byte) (*chainhash.Hash, error) {
return chainhash.NewHash(data)
}
26 changes: 26 additions & 0 deletions types/btc_header_hash_bytes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,29 @@ func FuzzHeaderHashBytesChainhashOps(f *testing.F) {
}
})
}

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

hexHash := datagen.GenRandomHexStr(types.BTCHeaderHashLen)
hexHash2 := datagen.GenRandomHexStr(types.BTCHeaderHashLen)
chHash, _ := chainhash.NewHashFromStr(hexHash)

var hbb, hbb2 types.BTCHeaderHashBytes
hbb.FromChainhash(chHash)
hbb2, _ = types.NewBTCHeaderHashBytesFromHex(hexHash2)

if hbb.String() != chHash.String() {
t.Errorf("String() method returned %s while %s was expected", hbb, chHash)
}

if !hbb.Eq(&hbb) {
t.Errorf("Object does not equal itself")
}
if hbb.Eq(&hbb2) {
t.Errorf("Object %s equals %s", hbb, hbb2)
}
})
}
14 changes: 12 additions & 2 deletions x/btclightclient/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ func CmdHashes() *cobra.Command {

queryClient := types.NewQueryClient(clientCtx)

params := types.NewQueryHashesRequest()
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

params := types.NewQueryHashesRequest(pageReq)
res, err := queryClient.Hashes(context.Background(), params)
if err != nil {
return err
Expand Down Expand Up @@ -116,7 +121,12 @@ func CmdMainChain() *cobra.Command {

queryClient := types.NewQueryClient(clientCtx)

params := types.NewQueryMainChainRequest()
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

params := types.NewQueryMainChainRequest(pageReq)
res, err := queryClient.MainChain(context.Background(), params)
if err != nil {
return err
Expand Down
5 changes: 4 additions & 1 deletion x/btclightclient/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import (

func TestGenesis(t *testing.T) {
headerBytes, _ := bbl.NewBTCHeaderBytesFromHex(types.DefaultBaseHeaderHex)
headerHash := headerBytes.Hash()
headerWork := types.CalcWork(&headerBytes)
baseHeaderInfo := types.NewBTCHeaderInfo(&headerBytes, headerHash, types.DefaultBaseHeaderHeight, &headerWork)

genesisState := types.GenesisState{
Params: types.DefaultParams(),
BaseBtcHeader: types.DefaultBaseBTCHeader(headerBytes, types.DefaultBaseHeaderHeight),
BaseBtcHeader: *baseHeaderInfo,
}

k, ctx := keepertest.BTCLightClientKeeper(t)
Expand Down
34 changes: 5 additions & 29 deletions x/btclightclient/keeper/base_btc_header.go
Original file line number Diff line number Diff line change
@@ -1,45 +1,21 @@
package keeper

import (
bbl "github.com/babylonchain/babylon/types"
"github.com/babylonchain/babylon/x/btclightclient/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)

func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) types.BaseBTCHeader {
baseBtcdHeader := k.HeadersState(ctx).GetBaseBTCHeader()

if baseBtcdHeader == nil {
return types.BaseBTCHeader{}
}

baseHash := baseBtcdHeader.BlockHash()
height, err := k.HeadersState(ctx).GetHeaderHeight(&baseHash)

if err != nil {
return types.BaseBTCHeader{}
}

headerBytes := bbl.NewBTCHeaderBytesFromBlockHeader(baseBtcdHeader)
return types.BaseBTCHeader{Header: &headerBytes, Height: height}
func (k Keeper) GetBaseBTCHeader(ctx sdk.Context) types.BTCHeaderInfo {
baseBtcHeader := k.HeadersState(ctx).GetBaseBTCHeader()
return *baseBtcHeader
}

// SetBaseBTCHeader checks whether a base BTC header exists and
// if not inserts it into storage
func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BaseBTCHeader) {
func (k Keeper) SetBaseBTCHeader(ctx sdk.Context, baseBTCHeader types.BTCHeaderInfo) {
existingHeader := k.HeadersState(ctx).GetBaseBTCHeader()
if existingHeader != nil {
panic("A base BTC Header has already been set")
}

btcdHeader := baseBTCHeader.Header.ToBlockHeader()

// The cumulative work for the Base BTC header is only the work
// for that particular header. This means that it is very important
// that no forks will happen that discard the base header because we
// will not be able to detect those. Cumulative work will build based
// on the sum of the work of the chain starting from the base header.
blockWork := types.CalcWork(btcdHeader)

k.HeadersState(ctx).CreateHeader(btcdHeader, baseBTCHeader.Height, blockWork)
k.HeadersState(ctx).CreateHeader(&baseBTCHeader)
}
35 changes: 16 additions & 19 deletions x/btclightclient/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ func (k Keeper) Hashes(ctx context.Context, req *types.QueryHashesRequest) (*typ

sdkCtx := sdk.UnwrapSDKContext(ctx)

// Ensure that the pagination key corresponds to hash bytes
if len(req.Pagination.Key) != 0 {
_, err := bbl.NewBTCHeaderHashBytesFromBytes(req.Pagination.Key)
if err != nil {
return nil, err
}
}

store := prefix.NewStore(k.HeadersState(sdkCtx).hashToHeight, types.HashToHeightPrefix)
pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) {
if accumulate {
Expand All @@ -50,8 +58,7 @@ func (k Keeper) Contains(ctx context.Context, req *types.QueryContainsRequest) (
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
sdkCtx := sdk.UnwrapSDKContext(ctx)
chHash := req.Hash.ToChainhash()
contains := k.HeadersState(sdkCtx).HeaderExists(chHash)
contains := k.HeadersState(sdkCtx).HeaderExists(req.Hash)
return &types.QueryContainsResponse{Contains: contains}, nil
}

Expand All @@ -67,19 +74,13 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest)
}
// If a starting key has not been set, then the first header is the tip
prevHeader := k.HeadersState(sdkCtx).GetTip()
prevHeaderHash := prevHeader.BlockHash()
prevHeaderHeight, err := k.HeadersState(sdkCtx).GetHeaderHeight(&prevHeaderHash)
if err != nil {
panic("Maintained header does not have a height")
}
// Otherwise, retrieve the header from the key
if len(req.Pagination.Key) != 0 {
headerHash, err := bbl.NewBTCHeaderHashBytesFromBytes(req.Pagination.Key)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "key does not correspond to a header hash")
}
chHash := headerHash.ToChainhash()
prevHeader, err = k.HeadersState(sdkCtx).GetHeaderByHash(chHash)
prevHeader, err = k.HeadersState(sdkCtx).GetHeaderByHash(&headerHash)
}

// If no tip exists or a key, then return an empty response
Expand All @@ -88,21 +89,18 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest)
}

var headers []*types.BTCHeaderInfo
currentHeight := prevHeaderHeight
headerInfo := types.NewBTCHeaderInfo(prevHeader, prevHeaderHeight)
headers = append(headers, headerInfo)
headers = append(headers, prevHeader)
store := prefix.NewStore(k.HeadersState(sdkCtx).headers, types.HeadersObjectPrefix)

// Set this value to true to signal to FilteredPaginate to iterate the entries in reverse
req.Pagination.Reverse = true
pageRes, err := query.FilteredPaginate(store, req.Pagination, func(_ []byte, value []byte, accumulate bool) (bool, error) {
if accumulate {
btcdHeader := blockHeaderFromStoredBytes(value)
headerInfo := headerInfoFromStoredBytes(k.cdc, value)
// If the previous block extends this block, then this block is part of the main chain
if prevHeader.PrevBlock.String() == btcdHeader.BlockHash().String() {
currentHeight -= 1
prevHeader = btcdHeader
headers = append(headers, types.NewBTCHeaderInfo(btcdHeader, currentHeight))
if prevHeader.HasParent(headerInfo) {
prevHeader = headerInfo
headers = append(headers, headerInfo)
}
}
return true, nil
Expand All @@ -114,8 +112,7 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest)

// Override the next key attribute to point to the parent of the last header
// instead of the next element contained in the store
prevBlockCh := prevHeader.PrevBlock
pageRes.NextKey = bbl.NewBTCHeaderHashBytesFromChainhash(&prevBlockCh)
pageRes.NextKey = prevHeader.Header.ParentHash().MustMarshal()

return &types.QueryMainChainResponse{Headers: headers, Pagination: pageRes}, nil
}
Loading

0 comments on commit fd02a3e

Please sign in to comment.