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

btclightclient: Cleanup codebase and add descriptive comments #50

Merged
merged 3 commits into from
Jul 6, 2022

Conversation

vitsalis
Copy link
Member

@vitsalis vitsalis commented Jul 6, 2022

Just some minor improvements based on previous changes and a set of (maybe overly descriptive) comments.

Comment on lines 75 to 78
prevHeader, err := k.HeadersState(sdkCtx).GetTip()
if err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

How can this fail? We start from genesis with a trusted header, and we always extend with valid blocks. There shouldn't be a semantic failure coming from it, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

It shouldn't fail. Actually, the GetTip() function only fails in the case that the tip is malformed. With that in mind, I will remove the returned error from the GetTip() method and add a panic.

Comment on lines 34 to 42
// Get necessary keys according
headersKey, err := types.HeadersObjectKey(height, &headerHash)
if err != nil {
return err
}
heightKey, err := types.HeadersObjectHeightKey(&headerHash)
if err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

How can these fail? We have a valid wire.BlockHeader so headerHash is legit. Why couldn't we generate keys from it.

Copy link
Member Author

Choose a reason for hiding this comment

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

These fail if the headerHash is not properly serialized into bytes. The chainhash.NewHash method returns an error and we handle the error by returning it. Do you think this should be a panicking occasion?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, we are using these 3rd party types because we assume they do the right thing. I'm not sure what chainhash.NewHash is called with this time, but if it's the hash calculated by wire.BlockHeader, wouldn't it be a WTF moment if it complained about it? If it was some random bytes from a gRPC request, then it can rightfully fail, but we should catch those early, so that the rest of the code can stop second guessing every step along the way.

Comment on lines 61 to 64
headersKey, err := types.HeadersObjectKey(height, hash)
if err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like hash is a domain type already, presumably validated. Why can't we turn it into a key with confidence? What can we do about such an error?

Comment on lines 71 to 75
// Get the BTCHeaderBytes object
headerBytes, err := bbl.NewBTCHeaderBytesFromBytes(rawBytes)
if err != nil {
return nil, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a record from our database, why couldn't we turn it back into headers?

Comment on lines 88 to 92
// Keyed by hash
hashKey, err := types.HeadersObjectHeightKey(hash)
if err != nil {
return 0, err
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question of how this can fail.

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

It is a good question of what should return an error.

On one hand, there's the philosophy of pushing out error handling to the caller, let them deal with it.

On the other, Go is an abysmal language when it comes to errors, it adds a lot of noise. And arguably in the blockchain context, if there is nothing we can do about the error, we don't necessarily want to reach consensus over how the application doesn't work, that is, if we return an error as a result of a transaction because we can't deserialise the tip from the database - that has nothing to do with the error, and hopefully doesn't affect all nodes. If it does, then we'll falsely make the user pay for what is not their fault.

So, perhaps it's better to be more restrained and only use errors where the problem was user input.

I'd defer to @SebastianElvis and @gitferry on how to go about this PR.

@vitsalis
Copy link
Member Author

vitsalis commented Jul 6, 2022

Thanks @aakoshh for the comments. I agree that some of those shouldn't be errors and those errors should not happen if the application works properly. However, many methods that are used throughout the codebase return errors (mainly those that are implemented by the BTCHeaderBytes and BTCHeaderHashBytes) which stem from the usage of the external btcd library (for example serializing/deseriliazing a block header or a block header hash).

The main question is: Should we leave those errors unhandled? In some occassions (mostly related to already stored data that should have been validated first), those errors should lead to panics because they definitely shouldn't happen.

Comment on lines 25 to 28
if err != nil {
return nil, err
}
return headerHashBytes, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this could panic. We have a domain type in chainhash.Hash. If that doesn't match what we think the correct length is, something is very wrong.

@aakoshh
Copy link
Contributor

aakoshh commented Jul 6, 2022

I know it's cumbersome but maybe a Must* variant can be used when we don't expect stuff to fail, when it comes to already validated data, and if it failed, it would be a showstopper. Unlike some random input in a transaction, which can never be trusted (unless it's already rejected during deserialization by a newtype).

@vitsalis
Copy link
Member Author

vitsalis commented Jul 6, 2022

@aakoshh Just pushed a major refactoring in which I follow a "fortress" approach. Basically, in order to become a BTCHeader{,Hash}Bytes object (Unmarshal), you go through strict checks that ensure that you have the proper properties for being that object, meaning that only corresponding header/hash bytes would become such objects without an error. Based on that, all Marshal methods now panic if the corresponding object cannot be converted to a hex/{block header,chainhash} etc.

This allowed to remove the bulk of errors and also improve type safety.

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

Awesome 🤩

@vitsalis vitsalis merged commit 39a1216 into main Jul 6, 2022
@vitsalis vitsalis deleted the btclightclient-cleanup branch July 6, 2022 14:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants