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

zoneconcierge API: find the BTC-finalised chain info before specific CZ height #264

Merged

Conversation

SebastianElvis
Copy link
Member

@SebastianElvis SebastianElvis commented Jan 9, 2023

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

  • implements a function FindClosestHeader that given a CZ height, return the indexed header that is closest (but no later than) this height
  • refactors FinalizedChainInfo so that FinalizedChainInfoUntilHeight can reuse some functionalities in it

This PR comes with a fuzz test for FindClosestHeader.

Logic of FinalizedChainInfoUntilHeight

Given the CZ height,

  • find the last finalised chain info
  • if the latest header in the chain info is no higher than the given height, do the same thing as in FinalizedChainInfo
  • if the latest header in the chain info is higher than the given height, then
    • find the closest header of the given height
    • get the epoch number of the header
    • do the same thing as in FinalizedChainInfo, except for using the new epoch number

Copy link
Member

@vitsalis vitsalis left a 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;
}


Copy link
Member

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
Copy link
Member

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()
Copy link
Member

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
Copy link
Member

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

Copy link
Member Author

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

ctx := sdk.UnwrapSDKContext(c)
resp := &types.QueryFinalizedChainInfoUntilHeightResponse{}

// find and assign the last finalised chain info and the earliest epoch that snapshots this epoch
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// 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

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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// starting from the requested height, find backward until a timestamped header
// starting from the requested height, iterate backward until a timestamped header

return resp, nil
}

// ProveFinalizedChainInfo generates proofs that a chainInfo has been finalised by the given epoch with epochInfo
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// 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)
Copy link
Collaborator

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice catch!

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
Copy link
Collaborator

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.

Copy link
Member Author

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?

Copy link
Collaborator

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

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
Copy link
Collaborator

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.

Copy link
Member Author

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.

Base automatically changed from api-count-checkpoints to dev January 9, 2023 23:13
@SebastianElvis
Copy link
Member Author

Thanks Vitalis and Konrad for the comments! I have addressed all comments and please feel free to review again.

@SebastianElvis SebastianElvis force-pushed the zoneconcierge-api-latest-finalised-epoch-info-before-height branch from 81840db to 04e16ce Compare January 10, 2023 02:46
if len(ed.Key) == 0 {
return 0, nil, nil, types.ErrNoCheckpointsForPreviousEpoch
}
bestSubmissionKey := ed.Key[0] // index of checkpoint tx on BTC
Copy link
Collaborator

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 that getEpochChanges which checks current status of submissions against light client and choses the best one. (You would need to pass nil as parentEpochBestSubmission to avoid check for parenthood)

Copy link
Member Author

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.

Copy link
Collaborator

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

@SebastianElvis SebastianElvis merged commit c2fd760 into dev Jan 10, 2023
@SebastianElvis SebastianElvis deleted the zoneconcierge-api-latest-finalised-epoch-info-before-height branch January 10, 2023 09:06
KonradStaniec added a commit that referenced this pull request Jan 12, 2023
* 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>
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.

3 participants