Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: btclightclient: gRPC query fuzz tests #69

Merged
merged 4 commits into from
Jul 21, 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
10 changes: 10 additions & 0 deletions testutil/datagen/btc_header_tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ func (t *BTCHeaderTree) RandomDescendant(node *blctypes.BTCHeaderInfo) *blctypes
return descendants[idx]
}

// GetHeadersMap returns a mapping between node hashes and nodes
func (t *BTCHeaderTree) GetHeadersMap() map[string]*blctypes.BTCHeaderInfo {
return t.headers
}

// Size returns the number of nodes that are maintained
func (t *BTCHeaderTree) Size() int {
return len(t.headers)
}

// getParent returns the parent of the node, or nil if it doesn't exist
func (t *BTCHeaderTree) getParent(node *blctypes.BTCHeaderInfo) *blctypes.BTCHeaderInfo {
if header, ok := t.headers[node.Header.ParentHash().String()]; ok {
Expand Down
11 changes: 11 additions & 0 deletions types/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package types

import "reflect"

func Reverse(s interface{}) {
n := reflect.ValueOf(s).Len()
swap := reflect.Swapper(s)
for i, j := 0, n-1; i < j; i, j = i+1, j-1 {
swap(i, j)
}
}
94 changes: 63 additions & 31 deletions x/btclightclient/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
bbl "github.com/babylonchain/babylon/types"
"github.com/babylonchain/babylon/x/btclightclient/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"google.golang.org/grpc/codes"
Expand Down Expand Up @@ -38,7 +37,7 @@ func (k Keeper) Hashes(ctx context.Context, req *types.QueryHashesRequest) (*typ
}
}

store := prefix.NewStore(k.headersState(sdkCtx).hashToHeight, types.HashToHeightPrefix)
store := k.headersState(sdkCtx).hashToHeight
pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key []byte, _ []byte, accumulate bool) (bool, error) {
if accumulate {
hashes = append(hashes, key)
Expand Down Expand Up @@ -72,47 +71,80 @@ func (k Keeper) MainChain(ctx context.Context, req *types.QueryMainChainRequest)
if req.Pagination == nil {
req.Pagination = &query.PageRequest{}
}
// If a starting key has not been set, then the first header is the tip
prevHeader := k.headersState(sdkCtx).GetTip()
// Otherwise, retrieve the header from the key

if req.Pagination.Limit == 0 {
req.Pagination.Limit = query.DefaultLimit
}

var keyHeader *types.BTCHeaderInfo
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")
}
prevHeader, err = k.headersState(sdkCtx).GetHeaderByHash(&headerHash)
}

// If no tip exists or a key, then return an empty response
if prevHeader == nil {
return &types.QueryMainChainResponse{}, nil
keyHeader, err = k.headersState(sdkCtx).GetHeaderByHash(&headerHash)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "header specified by key does not exist")
}
}

var headers []*types.BTCHeaderInfo
headers = append(headers, prevHeader)
store := prefix.NewStore(k.headersState(sdkCtx).headers, types.HeadersObjectPrefix)
var nextKey []byte
if req.Pagination.Reverse {
var start, end uint64
baseHeader := k.headersState(sdkCtx).GetBaseBTCHeader()
// The base header is located at the end of the mainchain
// which requires starting at the end
mainchain := k.headersState(sdkCtx).GetMainChain()
// Reverse the mainchain -- we want to retrieve results starting from the base header
Copy link
Contributor

Choose a reason for hiding this comment

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

The accepted answer at https://stackoverflow.com/questions/28058278/how-do-i-reverse-a-slice-in-go shows a generic variant; another one is in golang/go#47988. Since you bumped the Go version to 1.18, maybe it's worth sticking it somewhere. It's bonkers to have to do this every time.

Copy link
Member Author

Choose a reason for hiding this comment

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

Implemented this under a types/utils.go global file.

bbl.Reverse(mainchain)
if keyHeader == nil {
keyHeader = baseHeader
start = 0
} else {
start = keyHeader.Height - baseHeader.Height
}
end = start + req.Pagination.Limit

// 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 {
headerInfo := headerInfoFromStoredBytes(k.cdc, value)
// If the previous block extends this block, then this block is part of the main chain
if prevHeader.HasParent(headerInfo) {
prevHeader = headerInfo
headers = append(headers, headerInfo)
}
if end >= uint64(len(mainchain)) {
end = uint64(len(mainchain))
}
return true, nil
})

if err != nil {
return nil, err
}
// If the header's position on the mainchain is larger than the entire mainchain, then it is not part of the mainchain
// Also, if the element at the header's position on the mainchain is not the provided one, then it is not part of the mainchain
if start >= uint64(len(mainchain)) || !mainchain[start].Eq(keyHeader) {
return nil, status.Error(codes.InvalidArgument, "header specified by key is not a part of the mainchain")
}
headers = mainchain[start:end]
if end < uint64(len(mainchain)) {
nextKey = mainchain[end].Hash.MustMarshal()
}
} else {
tip := k.headersState(sdkCtx).GetTip()
// If there is no starting key, then the starting header is the tip
if keyHeader == nil {
keyHeader = tip
}
// This is the depth in which the start header should in the mainchain
startHeaderDepth := tip.Height - keyHeader.Height
// The depth that we want to retrieve up to
// -1 because the depth denotes how many headers have been built on top of it
depth := startHeaderDepth + req.Pagination.Limit - 1
// Retrieve the mainchain up to the depth
mainchain := k.headersState(sdkCtx).GetMainChainUpTo(depth)
// Check whether the key provided is part of the mainchain
if uint64(len(mainchain)) <= startHeaderDepth || !mainchain[startHeaderDepth].Eq(keyHeader) {
return nil, status.Error(codes.InvalidArgument, "header specified by key is not a part of the mainchain")
}

// Override the next key attribute to point to the parent of the last header
// instead of the next element contained in the store
pageRes.NextKey = prevHeader.Header.ParentHash().MustMarshal()
// The next key is the last elements parent hash
nextKey = mainchain[len(mainchain)-1].Header.ParentHash().MustMarshal()
headers = mainchain[startHeaderDepth:]
}

pageRes := &query.PageResponse{
NextKey: nextKey,
}
// The headers that we should return start from the depth of the start header
return &types.QueryMainChainResponse{Headers: headers, Pagination: pageRes}, nil
}
Loading