-
Notifications
You must be signed in to change notification settings - Fork 170
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: Refactor keepers based on needs of btcheckpoint module #72
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -142,21 +142,20 @@ func (k Keeper) InsertHeader(ctx sdk.Context, header *bbl.BTCHeaderBytes) error | |
} | ||
|
||
// BlockHeight returns the height of the provided header | ||
func (k Keeper) BlockHeight(ctx sdk.Context, header *bbl.BTCHeaderBytes) (uint64, error) { | ||
if header == nil { | ||
func (k Keeper) BlockHeight(ctx sdk.Context, headerHash *bbl.BTCHeaderHashBytes) (uint64, error) { | ||
if headerHash == nil { | ||
return 0, types.ErrEmptyMessage | ||
} | ||
headerHash := header.Hash() | ||
return k.headersState(ctx).GetHeaderHeight(headerHash) | ||
} | ||
|
||
// MainChainDepth returns the depth of the header in the main chain or -1 if it does not exist in it | ||
func (k Keeper) MainChainDepth(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes) (int64, error) { | ||
if headerBytes == nil { | ||
func (k Keeper) MainChainDepth(ctx sdk.Context, headerHashBytes *bbl.BTCHeaderHashBytes) (int64, error) { | ||
if headerHashBytes == nil { | ||
return -1, types.ErrEmptyMessage | ||
} | ||
// Retrieve the header. If it does not exist, return an error | ||
headerInfo, err := k.headersState(ctx).GetHeaderByHash(headerBytes.Hash()) | ||
headerInfo, err := k.headersState(ctx).GetHeaderByHash(headerHashBytes) | ||
if err != nil { | ||
return -1, err | ||
} | ||
|
@@ -185,11 +184,11 @@ func (k Keeper) MainChainDepth(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes) | |
} | ||
|
||
// IsHeaderKDeep returns true if a header is at least k-deep on the main chain | ||
func (k Keeper) IsHeaderKDeep(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes, depth uint64) (bool, error) { | ||
if headerBytes == nil { | ||
func (k Keeper) IsHeaderKDeep(ctx sdk.Context, headerHashBytes *bbl.BTCHeaderHashBytes, depth uint64) (bool, error) { | ||
if headerHashBytes == nil { | ||
return false, types.ErrEmptyMessage | ||
} | ||
mainchainDepth, err := k.MainChainDepth(ctx, headerBytes) | ||
mainchainDepth, err := k.MainChainDepth(ctx, headerHashBytes) | ||
if err != nil { | ||
return false, err | ||
} | ||
|
@@ -198,6 +197,42 @@ func (k Keeper) IsHeaderKDeep(ctx sdk.Context, headerBytes *bbl.BTCHeaderBytes, | |
return false, nil | ||
} | ||
// return true if the provided depth is more than equal the mainchain depth | ||
//panic(fmt.Sprintf("%d %d", depth, mainchainDepth)) | ||
return depth >= uint64(mainchainDepth), nil | ||
} | ||
|
||
// IsAncestor returns true/false depending on whether `parent` is an ancestor of `child`. | ||
// Returns false if the parent and the child are the same header. | ||
func (k Keeper) IsAncestor(ctx sdk.Context, parentHashBytes *bbl.BTCHeaderHashBytes, childHashBytes *bbl.BTCHeaderHashBytes) (bool, error) { | ||
// nil checks | ||
if parentHashBytes == nil || childHashBytes == nil { | ||
return false, types.ErrEmptyMessage | ||
} | ||
// Retrieve parent and child header | ||
parentHeader, err := k.headersState(ctx).GetHeaderByHash(parentHashBytes) | ||
if err != nil { | ||
return false, types.ErrHeaderDoesNotExist.Wrapf("parent does not exist") | ||
} | ||
childHeader, err := k.headersState(ctx).GetHeaderByHash(childHashBytes) | ||
if err != nil { | ||
return false, types.ErrHeaderDoesNotExist.Wrapf("child does not exist") | ||
} | ||
|
||
// If the height of the child is less than the parent, then the input is invalid | ||
if childHeader.Height < parentHeader.Height { | ||
return false, types.ErrInvalidAncestor.Wrapf("ancestor height is larger than descendant height") | ||
} | ||
|
||
// If they have the same height, then the result is false | ||
if childHeader.Height == parentHeader.Height { | ||
return false, nil | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just curious why you don't consider this to be an error as well, or the why isn't the previous not an error if this one isn't. It sounds like either the caller should know that the block behind |
||
|
||
// Retrieve the ancestry | ||
ancestry := k.headersState(ctx).GetHeaderAncestryUpTo(childHeader, childHeader.Height-parentHeader.Height) | ||
// If it is empty, return false | ||
if len(ancestry) == 0 { | ||
return false, nil | ||
} | ||
// Return whether the last element of the ancestry is equal to the parent | ||
return ancestry[len(ancestry)-1].Eq(parentHeader), nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ func FuzzKeeperIsHeaderKDeep(f *testing.F) { | |
|
||
// Test header not existing | ||
nonExistentHeader := datagen.GenRandomBTCHeaderBytes(nil, nil) | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, &nonExistentHeader, depth) | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, nonExistentHeader.Hash(), depth) | ||
if err == nil { | ||
t.Errorf("Non existent header led to nil error") | ||
} | ||
|
@@ -62,7 +62,7 @@ func FuzzKeeperIsHeaderKDeep(f *testing.F) { | |
mainchain := tree.GetMainChain() | ||
// Select a random depth based on the main-chain length | ||
randDepth := uint64(rand.Int63n(int64(len(mainchain)))) | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Header, randDepth) | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Hash, randDepth) | ||
// Identify whether the function should return true or false | ||
headerDepth := tip.Height - header.Height | ||
// If the random depth that we chose is more than the headerDepth, then it should return true | ||
|
@@ -76,7 +76,7 @@ func FuzzKeeperIsHeaderKDeep(f *testing.F) { | |
} else { | ||
// The depth provided does not matter, we should always get false. | ||
randDepth := rand.Uint64() | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Header, randDepth) | ||
isDeep, err = blcKeeper.IsHeaderKDeep(ctx, header.Hash, randDepth) | ||
if err != nil { | ||
t.Errorf("Existent header led to a non-nil error %s", err) | ||
} | ||
|
@@ -117,7 +117,7 @@ func FuzzKeeperMainChainDepth(f *testing.F) { | |
|
||
// Test header not existing | ||
nonExistentHeader := datagen.GenRandomBTCHeaderBytes(nil, nil) | ||
depth, err = blcKeeper.MainChainDepth(ctx, &nonExistentHeader) | ||
depth, err = blcKeeper.MainChainDepth(ctx, nonExistentHeader.Hash()) | ||
if err == nil { | ||
t.Errorf("Non existent header led to nil error") | ||
} | ||
|
@@ -134,7 +134,7 @@ func FuzzKeeperMainChainDepth(f *testing.F) { | |
// Otherwise, the result should always be -1 | ||
tip := tree.GetTip() | ||
// Get the depth | ||
depth, err = blcKeeper.MainChainDepth(ctx, header.Header) | ||
depth, err = blcKeeper.MainChainDepth(ctx, header.Hash) | ||
if err != nil { | ||
t.Errorf("Existent and header led to error") | ||
} | ||
|
@@ -182,7 +182,7 @@ func FuzzKeeperBlockHeight(f *testing.F) { | |
|
||
// Test header not existing | ||
nonExistentHeader := datagen.GenRandomBTCHeaderBytes(nil, nil) | ||
height, err = blcKeeper.BlockHeight(ctx, &nonExistentHeader) | ||
height, err = blcKeeper.BlockHeight(ctx, nonExistentHeader.Hash()) | ||
if err == nil { | ||
t.Errorf("Non existent header led to nil error") | ||
} | ||
|
@@ -192,7 +192,7 @@ func FuzzKeeperBlockHeight(f *testing.F) { | |
|
||
tree := genRandomTree(blcKeeper, ctx, 1, 10) | ||
header := tree.RandomNode() | ||
height, err = blcKeeper.BlockHeight(ctx, header.Header) | ||
height, err = blcKeeper.BlockHeight(ctx, header.Hash) | ||
if err != nil { | ||
t.Errorf("Existent header led to an error") | ||
} | ||
|
@@ -202,6 +202,92 @@ func FuzzKeeperBlockHeight(f *testing.F) { | |
}) | ||
} | ||
|
||
func FuzzKeeperIsAncestor(f *testing.F) { | ||
/* | ||
Checks: | ||
1. If the child hash or the parent hash are nil, an error is returned | ||
2. If the child has a lower height than the parent, an error is returned | ||
3. If the child and the parent are the same, false is returned | ||
4. If the parent is an ancestor of child then `true` is returned. | ||
|
||
Data generation: | ||
- Generate a random tree of headers and insert it into storage. | ||
- Select a random header and select a random descendant and a random ancestor to test (2-4). | ||
*/ | ||
datagen.AddRandomSeedsToFuzzer(f, 100) | ||
f.Fuzz(func(t *testing.T, seed int64) { | ||
rand.Seed(seed) | ||
blcKeeper, ctx := testkeeper.BTCLightClientKeeper(t) | ||
|
||
nonExistentParent := datagen.GenRandomBTCHeaderInfo() | ||
nonExistentChild := datagen.GenRandomBTCHeaderInfo() | ||
|
||
// nil inputs test | ||
isAncestor, err := blcKeeper.IsAncestor(ctx, nil, nil) | ||
if err == nil { | ||
t.Errorf("Nil input led to nil error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Nil input led to true result") | ||
} | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, nonExistentParent.Hash, nil) | ||
if err == nil { | ||
t.Errorf("Nil input led to nil error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Nil input led to true result") | ||
} | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, nil, nonExistentChild.Hash) | ||
if err == nil { | ||
t.Errorf("Nil input led to nil error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Nil input led to true result") | ||
} | ||
|
||
// non-existent test | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, nonExistentParent.Hash, nonExistentChild.Hash) | ||
if err == nil { | ||
t.Errorf("Non existent headers led to nil error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Non existent headers led to true result") | ||
} | ||
|
||
// Generate random tree of headers | ||
tree := genRandomTree(blcKeeper, ctx, 1, 10) | ||
header := tree.RandomNode() | ||
ancestor := tree.RandomNode() | ||
|
||
if ancestor.Eq(header) { | ||
// Same headers test | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) | ||
if err != nil { | ||
t.Errorf("Valid input led to an error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Same header input led to true result") | ||
} | ||
} else if ancestor.Height > header.Height { // Descendant test | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) | ||
if err == nil { | ||
t.Errorf("Providing a descendant as a parent led to a nil error") | ||
} | ||
if isAncestor { | ||
t.Errorf("Providing a descendant as a parent led to a true result") | ||
} | ||
} else { // Ancestor test | ||
isAncestor, err = blcKeeper.IsAncestor(ctx, ancestor.Hash, header.Hash) | ||
if err != nil { | ||
t.Errorf("Valid input led to an error") | ||
} | ||
if isAncestor != tree.IsOnNodeChain(header, ancestor) { // The result should be whether it is an ancestor or not | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For completeness sake I will say it it again: instead of having an algorithm to check, you should be able to generate data knowing what the outcome can be by picking a random ancestor and extending with another tree. Then you can pick any node in the extension and it's a descendant. The only question is is whether you can easily pick a node that doesn't have other descendants in the original tree. But if you want to keep it this way that's fine as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Overall, I agree. In this case, we can get an ancestor or a descendant through the usage of the However, in this test I follow this approach in order to get two birds with one stone. Specifically, I want to test that the function returns true if the node is an anecstor and false otherwise. Randomness ensures that both the test cases are going to be tested. I don't see any difference on using the |
||
t.Errorf("Got invalid ancestry result. Expected %t, got %t", tree.IsOnNodeChain(header, ancestor), isAncestor) | ||
} | ||
} | ||
}) | ||
} | ||
|
||
func FuzzKeeperInsertHeader(f *testing.F) { | ||
/* | ||
Checks: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One comment to arguments, why using passing by pointer not by value ? In general using pointer should be used when passing larger structs to avoid copying or when arguments are optional. Imo both are not true here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do this mostly out of convenience. Due to the usage of the
customtype
attribute on the proto files, most elements are pointers toBTCHeaderBytes
orBTCHeaderHashBytes
objects. If the parameters are not passed as pointers, then we would have a bunch of pointer dereferences in various places and extra checks, so I prefer to delegate those checks to when the elements are actually getting used.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so I understand this convenience argument although not fully agree with it as:
IsAncestor
withnil
hashes does not really have any semantic meaning.IsAncestor
does not have full context of the caller ifnil
pointer is really error or rather a panic for example, when receiving input form external source like lets say json rpc this is really an error , but retrieving this hash from our local database it should rather be panic as we should not save nil stuf in our database. (unelss of course we use pointer to represent optional value)Nevertheless as was merged first I will adapt my pr to use pointers, so that interfaces will allign,