-
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
zoneconcierge API: find the BTC-finalised chain info before specific CZ height #264
zoneconcierge API: find the BTC-finalised chain info before specific CZ height #264
Conversation
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.
Great work! Few comments:
@@ -151,3 +155,44 @@ message QueryFinalizedChainInfoResponse { | |||
// It is the two TransactionInfo in the best (i.e., earliest) checkpoint submission | |||
repeated babylon.btccheckpoint.v1.TransactionInfo proof_epoch_submitted = 8; | |||
} | |||
|
|||
|
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.
Extra empty line.
sdkerrors "cosmossdk.io/errors" | ||
"github.com/babylonchain/babylon/x/zoneconcierge/types" | ||
"github.com/cosmos/cosmos-sdk/store/prefix" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// FindClosestHeader finds the IndexedHeader that is closest to (but not after) the given height | ||
// TODO: test |
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 see tests for this in this PR.
return nil, fmt.Errorf("chain with ID %s does not have a timestamped header before height %d", chainID, height) | ||
} | ||
// find the header in bytes, decode and return | ||
headerBytes := iter.Value() |
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.
Very nice way of finding the closest header 👏
require.NoError(t, err) | ||
require.Equal(t, headers[len(headers)-1].Header.LastCommitHash, header.Hash) | ||
|
||
// skip some a non-zero number of headers in between |
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 don't entirely understand this sentence
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.
Sorry that's a typo 😢 I was meaning that here we skip a non-zero number of headers in between, in order to create a gap of non-timestamped headers. Fixed
x/zoneconcierge/keeper/grpc_query.go
Outdated
ctx := sdk.UnwrapSDKContext(c) | ||
resp := &types.QueryFinalizedChainInfoUntilHeightResponse{} | ||
|
||
// find and assign the last finalised chain info and the earliest epoch that snapshots this epoch |
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.
// find and assign the last finalised chain info and the earliest epoch that snapshots this epoch | |
// find and assign the last finalised chain info and the earliest epoch that snapshots this chain ID |
x/zoneconcierge/keeper/grpc_query.go
Outdated
return nil, err | ||
} | ||
} else { // the requested height is before the last finalised chain info | ||
// starting from the requested height, find backward until a timestamped header |
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.
// starting from the requested height, find backward until a timestamped header | |
// starting from the requested height, iterate backward until a timestamped header |
x/zoneconcierge/keeper/grpc_query.go
Outdated
return resp, nil | ||
} | ||
|
||
// ProveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo |
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.
// ProveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo | |
// proveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo |
// the requested height is lower than the latest header, trace back until finding a timestamped header | ||
store := k.canonicalChainStore(ctx, chainID) | ||
heightBytes := sdk.Uint64ToBigEndian(height) | ||
iter := store.ReverseIterator(nil, heightBytes) |
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.
iterators need to be closed, defer iter.close()
after this line should do the trick.
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.
Nice catch!
x/zoneconcierge/keeper/epochs.go
Outdated
epochingtypes "github.com/babylonchain/babylon/x/epoching/types" | ||
"github.com/babylonchain/babylon/x/zoneconcierge/types" | ||
sdk "github.com/cosmos/cosmos-sdk/types" | ||
) | ||
|
||
// GetCkptInfoForFinalizedEpoch gets the raw checkpoint and the associated best submission of a finalised epoch | ||
// CONTRACT: the function can only take an epoch that has already been finalised as input |
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.
this is public function, at some point someone will do not read this comment and break this invariant. In general I would avoid panicking in public function. I would either return error in case of providing not finalized epoch number or restructure code somehow.
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.
Good point. This code snippet was not a function and panicking was expected in that scenario (specifically in RPC FinalizedChainInfo
and FinalizedChainInfoUntilHeight
). Given that this is just a private helper function dedicated for ZoneConcierge, I changed it to a private function. Then panic might make sense. Wdyt?
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.
Sounds good. I have nothing against panicking in private helpers, if some module invariants are broken due to programming error
x/zoneconcierge/keeper/epochs.go
Outdated
err := fmt.Errorf("finalized epoch %d should have at least 1 checkpoint submission", epochNumber) | ||
panic(err) // this can only be a programming error | ||
} | ||
bestSubmissionKey := ed.Key[0] // index of checkpoint tx on BTC |
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.
hmm the fact that finalized epoch must have one submission keys, which is best submission is a bit implementation detail and the fact that we are doing this check:
if len(ed.Key) == 0 {
err := fmt.Errorf("finalized epoch %d should have at least 1 checkpoint submission", epochNumber)
panic(err) // this can only be a programming error
}
is a bit of a smell that something is not right.
Maybe btccheckpoing module should have another query like getEpochBestSubmission
which would return something along:
EpochInfo {
epochStatus: Status
bestCheckpointBytes: []bytes
}
?
Then all this checks would become internal to btcheckpoint module. In general GetEpochData
should be private to btcheckpoint module (my bad 😅 ) , and there should be separate query for epoch info, which would honor all invariants.
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.
Yeah I feel it's better to make the btccheckpoint module to provide the functionality of choosing the best submission. I have implemented a function GetEpochDataWithBestSubmission
in btccheckpoint to this end, and removed the contracts/panics in the original implementation.
Nevertheless, I left GetEpochData
to be public, since this will lead to a lot of changes (e.g., btccheckpoint's testkeeper requires it to be public). Perhaps we can do this in a future PR.
Thanks Vitalis and Konrad for the comments! I have addressed all comments and please feel free to review again. |
81840db
to
04e16ce
Compare
if len(ed.Key) == 0 { | ||
return 0, nil, nil, types.ErrNoCheckpointsForPreviousEpoch | ||
} | ||
bestSubmissionKey := ed.Key[0] // index of checkpoint tx on BTC |
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.
take in mind that this holds only for finalized epoch i.e only for finalized epoch we can be sure that ed.Key[0]
is the best submission. There are 2 ways around it:
- make this function super specific like
GetEpochDataWithBestSubmissionFromFinalizedEpoch
and if epoch is not finalized return error - return
ed.Key[0]
if epoch is finalized, otherwise chose best submission. There is a function for thatgetEpochChanges
which checks current status of submissions against light client and choses the best one. (You would need to passnil
asparentEpochBestSubmission
to avoid check for parenthood)
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 see. Thanks for clarifying the choice of best submissions. Since the function is only used in ZoneConcierge, the first option might be cleaner. I have changed the function name to GetFinalizedEpochDataWithBestSubmission
and added an extra check. Please have a look again.
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.
given specific usage of this function I also think so, if it ever become used in any other context we may need to re-think that
* zoneconcierge: API for querying the chain info of a given epoch (#260) * zoneconcierge: API for querying headers in a given epoch (#261) * zoneconcierge API: adding total number of timestamped headers to chainInfo (#263) * zoneconcierge API: find the BTC-finalised chain info before specific CZ height (#264) * zoneconcierge API: find header and fork headers at a given height (#266) * checkpointing API: add checkpoint lifecycle in `RawCheckpointWithMeta` (#267) * chore: refactor `FinalizedChainInfo` API (#268) * Add integration test for zoneconcierge checkpointing (#269) * Add integration test checking info about opposing cz chain * Fix: Increase gas in e2e test (#270) Increase gas * fix: checkpointing: Do not make the `home` flag a required one and unmarshall PubKey (#271) * Fix build Co-authored-by: Runchao Han <me@runchao.rocks> Co-authored-by: Vitalis Salis <VitSalis@gmail.com>
Fixes BE-13
Context: point 2 of https://babylon-chain.atlassian.net/wiki/spaces/BABYLON/pages/50955614/The+Oracle#1.-Using-the-Oracle
This PR provides the API that given a CZ height, return the BTC-finalized chain info no later than this CZ height.
Along the way, this PR
FindClosestHeader
that given a CZ height, return the indexed header that is closest (but no later than) this heightFinalizedChainInfo
so thatFinalizedChainInfoUntilHeight
can reuse some functionalities in itThis PR comes with a fuzz test for
FindClosestHeader
.Logic of FinalizedChainInfoUntilHeight
Given the CZ height,
FinalizedChainInfo
FinalizedChainInfo
, except for using the new epoch number