Skip to content

Commit

Permalink
rely on voting power query
Browse files Browse the repository at this point in the history
  • Loading branch information
gitferry committed Aug 20, 2024
1 parent e5681b1 commit 883a406
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 183 deletions.
53 changes: 21 additions & 32 deletions clientcontroller/babylon.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
btclctypes "github.com/babylonlabs-io/babylon/x/btclightclient/types"
btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
ckpttypes "github.com/babylonlabs-io/babylon/x/checkpointing/types"
finalitytypes "github.com/babylonlabs-io/babylon/x/finality/types"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
Expand Down Expand Up @@ -375,30 +374,6 @@ func (bc *BabylonController) QueryBestBlock() (*types.BlockInfo, error) {
return blocks[0], nil
}

func (bc *BabylonController) QueryIsPubRandTimestamped(height uint64) (bool, error) {
epochParamResp, err := bc.bbnClient.EpochingParams()
if err != nil {
return false, fmt.Errorf("failed to query epoching params: %w", err)
}

// TODO should implement the height epoch conversion as Babylon library
committedEpoch, err := fromHeightToEpoch(height, epochParamResp.Params.EpochInterval)
if err != nil {
return false, fmt.Errorf("failed to get the epoch number of height %d", height)
}

lastFinalizedEpochResp, err := bc.bbnClient.LatestEpochFromStatus(ckpttypes.Finalized)
if err != nil {
return false, fmt.Errorf("failed to query last finalized epoch: %w", err)
}

if committedEpoch > lastFinalizedEpochResp.RawCheckpoint.EpochNum {
return false, nil
}

return true, nil
}

func (bc *BabylonController) queryCometBestBlock() (*types.BlockInfo, error) {
ctx, cancel := getContextWithCancel(bc.cfg.Timeout)
// this will return 20 items at max in the descending order (highest first)
Expand Down Expand Up @@ -518,6 +493,15 @@ func (bc *BabylonController) QueryBtcLightClientTip() (*btclctypes.BTCHeaderInfo
return res.Header, nil
}

func (bc *BabylonController) QueryCurrentEpoch() (uint64, error) {
res, err := bc.bbnClient.QueryClient.CurrentEpoch()
if err != nil {
return 0, fmt.Errorf("failed to query BTC tip: %v", err)
}

return res.CurrentEpoch, nil
}

func (bc *BabylonController) QueryVotesAtHeight(height uint64) ([]bbntypes.BIP340PubKey, error) {
res, err := bc.bbnClient.QueryClient.VotesAtHeight(height)
if err != nil {
Expand Down Expand Up @@ -615,15 +599,20 @@ func (bc *BabylonController) SubmitCovenantSigs(
return &types.TxResponse{TxHash: res.TxHash, Events: res.Events}, nil
}

func fromHeightToEpoch(height, epochInterval uint64) (uint64, error) {
if height == 0 {
return 0, fmt.Errorf("block height should be positive")
func (bc *BabylonController) GetBBNClient() *bbnclient.Client {
return bc.bbnClient
}

func (bc *BabylonController) InsertSpvProofs(submitter string, proofs []*btcctypes.BTCSpvProof) (*provider.RelayerTxResponse, error) {
msg := &btcctypes.MsgInsertBTCSpvProof{
Submitter: submitter,
Proofs: proofs,
}

epochNum := height / epochInterval
if height%epochInterval > 0 {
epochNum++
res, err := bc.reliablySendMsg(msg, emptyErrs, emptyErrs)
if err != nil {
return nil, err
}

return epochNum, nil
return res, nil
}
3 changes: 0 additions & 3 deletions clientcontroller/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ type ClientController interface {
// error will be returned if the consumer chain has not been activated
QueryActivatedHeight() (uint64, error)

// QueryIsPubRandTimestamped returns whether the public randomness at the given height has been BTC-timestamped
QueryIsPubRandTimestamped(height uint64) (bool, error)

Close() error
}

Expand Down
8 changes: 0 additions & 8 deletions finality-provider/service/fastsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,6 @@ func (fp *FinalityProviderInstance) FastSync(startHeight, endHeight uint64) (*Fa
fp.metrics.IncrementFpTotalBlocksWithoutVotingPower(fp.GetBtcPkHex())
continue
}
// check whether there is timestamped public randomness on Babylon for the height
hasRand, err := fp.hasTimestampedRandomness(b.Height)
if err != nil {
return nil, err
}
if !hasRand {
break
}
// all good, add the block for catching up
catchUpBlocks = append(catchUpBlocks, b)
}
Expand Down
62 changes: 42 additions & 20 deletions finality-provider/service/fastsync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"math/rand"
"testing"

"github.com/babylonlabs-io/babylon/testutil/datagen"
ftypes "github.com/babylonlabs-io/babylon/x/finality/types"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

Expand All @@ -12,10 +14,10 @@ import (
)

// FuzzFastSync_SufficientRandomness tests a case where we have sufficient
// randomness timestamped and voting power when the finality provider enters
// fast-sync it is expected that the finality provider could catch up to
// the current height through fast-sync
func FuzzFastSync_SufficientRandomnessTimestamped(f *testing.F) {
// randomness and voting power when the finality provider enters fast-sync
// it is expected that the finality provider could catch up to the current
// height through fast-sync
func FuzzFastSync_SufficientRandomness(f *testing.F) {
testutil.AddRandomSeedsToFuzzer(f, 10)
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))
Expand All @@ -36,8 +38,15 @@ func FuzzFastSync_SufficientRandomnessTimestamped(f *testing.F) {

mockClientController.EXPECT().QueryFinalityProviderVotingPower(fpIns.GetBtcPk(), gomock.Any()).
Return(uint64(1), nil).AnyTimes()
// make sure all the public randomness is timestamped
mockClientController.EXPECT().QueryIsPubRandTimestamped(gomock.Any()).Return(true, nil).AnyTimes()
// the last committed height is higher than the current height
// to make sure the randomness is sufficient
lastCommittedHeight := randomStartingHeight + testutil.TestPubRandNum
lastCommittedPubRandMap := make(map[uint64]*ftypes.PubRandCommitResponse)
lastCommittedPubRandMap[lastCommittedHeight] = &ftypes.PubRandCommitResponse{
NumPubRand: 1000,
Commitment: datagen.GenRandomByteArray(r, 32),
}
mockClientController.EXPECT().QueryLastCommittedPublicRand(gomock.Any(), uint64(1)).Return(lastCommittedPubRandMap, nil).AnyTimes()

catchUpBlocks := testutil.GenBlocks(r, finalizedHeight+1, currentHeight)
expectedTxHash := testutil.GenRandomHexStr(r, 32)
Expand All @@ -56,16 +65,16 @@ func FuzzFastSync_SufficientRandomnessTimestamped(f *testing.F) {
})
}

// FuzzFastSync_NoTimestampedRandomness tests a case where we have sufficient
// randomness submitted and voting power when the finality provider enters fast-sync
// but the randomness is not BTC-timestamped
// it is expected that the finality provider cannot catch up
func FuzzFastSync_NoTimestampedRandomness(f *testing.F) {
// FuzzFastSync_NoRandomness tests a case where we have insufficient
// randomness but with voting power when the finality provider enters fast-sync
// it is expected that the finality provider could catch up to the last
// committed height
func FuzzFastSync_NoRandomness(f *testing.F) {
testutil.AddRandomSeedsToFuzzer(f, 10)
f.Fuzz(func(t *testing.T, seed int64) {
r := rand.New(rand.NewSource(seed))

randomStartingHeight := uint64(r.Int63n(100) + 1)
randomStartingHeight := uint64(r.Int63n(100) + 100)
finalizedHeight := randomStartingHeight + uint64(r.Int63n(10)+2)
currentHeight := finalizedHeight + uint64(r.Int63n(10)+1)
mockClientController := testutil.PrepareMockedClientController(t, r, randomStartingHeight, currentHeight)
Expand All @@ -79,24 +88,37 @@ func FuzzFastSync_NoTimestampedRandomness(f *testing.F) {
_, err := fpIns.CommitPubRand(randomStartingHeight)
require.NoError(t, err)

mockClientController.EXPECT().QueryFinalityProviderVotingPower(fpIns.GetBtcPk(), gomock.Any()).
Return(uint64(1), nil).AnyTimes()
// make sure no randomness is timestamped
mockClientController.EXPECT().QueryIsPubRandTimestamped(gomock.Any()).Return(false, nil).AnyTimes()
// the last height with pub rand is a random value inside [finalizedHeight+1, currentHeight]
lastHeightWithPubRand := uint64(rand.Intn(int(currentHeight)-int(finalizedHeight))) + finalizedHeight + 1
for i := randomStartingHeight; i <= currentHeight; i++ {
if i <= lastHeightWithPubRand {
mockClientController.EXPECT().QueryFinalityProviderVotingPower(fpIns.GetBtcPk(), i).
Return(uint64(1), nil).AnyTimes()
} else {
mockClientController.EXPECT().QueryFinalityProviderVotingPower(fpIns.GetBtcPk(), i).
Return(uint64(0), nil).AnyTimes()
}
}
lastCommittedPubRandMap := make(map[uint64]*ftypes.PubRandCommitResponse)
lastCommittedPubRandMap[lastHeightWithPubRand-10] = &ftypes.PubRandCommitResponse{
NumPubRand: 10 + 1,
Commitment: datagen.GenRandomByteArray(r, 32),
}
mockClientController.EXPECT().QueryLastCommittedPublicRand(gomock.Any(), uint64(1)).Return(lastCommittedPubRandMap, nil).AnyTimes()

catchUpBlocks := testutil.GenBlocks(r, finalizedHeight+1, currentHeight)
expectedTxHash := testutil.GenRandomHexStr(r, 32)
finalizedBlock := &types.BlockInfo{Height: finalizedHeight, Hash: testutil.GenRandomByteArray(r, 32)}
mockClientController.EXPECT().QueryLatestFinalizedBlocks(uint64(1)).Return([]*types.BlockInfo{finalizedBlock}, nil).AnyTimes()
mockClientController.EXPECT().QueryBlocks(finalizedHeight+1, currentHeight, uint64(10)).
Return(catchUpBlocks, nil)
mockClientController.EXPECT().SubmitBatchFinalitySigs(fpIns.GetBtcPk(), catchUpBlocks, gomock.Any(), gomock.Any(), gomock.Any()).
mockClientController.EXPECT().SubmitBatchFinalitySigs(fpIns.GetBtcPk(), catchUpBlocks[:lastHeightWithPubRand-finalizedHeight], gomock.Any(), gomock.Any(), gomock.Any()).
Return(&types.TxResponse{TxHash: expectedTxHash}, nil).AnyTimes()
result, err := fpIns.FastSync(finalizedHeight+1, currentHeight)
require.NoError(t, err)
require.NotNil(t, result)
require.Equal(t, 0, len(result.Responses))
require.Equal(t, uint64(0), fpIns.GetLastVotedHeight())
require.Equal(t, uint64(0), fpIns.GetLastProcessedHeight())
require.Equal(t, expectedTxHash, result.Responses[0].TxHash)
require.Equal(t, lastHeightWithPubRand, fpIns.GetLastVotedHeight())
require.Equal(t, lastHeightWithPubRand, fpIns.GetLastProcessedHeight())
})
}
92 changes: 0 additions & 92 deletions finality-provider/service/fp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,22 +203,6 @@ func (fp *FinalityProviderInstance) finalitySigSubmissionLoop() {
fp.metrics.IncrementFpTotalBlocksWithoutVotingPower(fp.GetBtcPkHex())
continue
}
// check whether the randomness has been committed
// the retry will end if max retry times is reached
// or the target block is finalized
isFinalized, err := fp.retryCheckRandomnessUntilBlockFinalized(b)
if err != nil {
if !errors.Is(err, ErrFinalityProviderShutDown) {
fp.reportCriticalErr(err)
}
break
}
// the block is finalized, no need to submit finality signature
if isFinalized {
fp.MustSetLastProcessedHeight(b.Height)
continue
}

// use the copy of the block to avoid the impact to other receivers
nextBlock := *b
res, err := fp.retrySubmitFinalitySignatureUntilBlockFinalized(&nextBlock)
Expand Down Expand Up @@ -431,11 +415,6 @@ func (fp *FinalityProviderInstance) hasVotingPower(b *types.BlockInfo) (bool, er
return true, nil
}

func (fp *FinalityProviderInstance) hasTimestampedRandomness(blockHeight uint64) (bool, error) {
// TODO add retry
return fp.cc.QueryIsPubRandTimestamped(blockHeight)
}

func (fp *FinalityProviderInstance) reportCriticalErr(err error) {
fp.criticalErrChan <- &CriticalError{
err: err,
Expand All @@ -448,77 +427,6 @@ func (fp *FinalityProviderInstance) checkLagging(currentBlock *types.BlockInfo)
return currentBlock.Height >= fp.GetLastProcessedHeight()+fp.cfg.FastSyncGap
}

// retryQueryingRandomnessUntilBlockFinalized periodically checks whether
// the randomness has been committed to the target block until the block is
// finalized
// error will be returned if maximum retries have been reached or the query to
// the consumer chain fails
func (fp *FinalityProviderInstance) retryCheckRandomnessUntilBlockFinalized(targetBlock *types.BlockInfo) (bool, error) {
var numRetries uint32

// we break the for loop if the block is finalized or the randomness is successfully committed
// error will be returned if maximum retries have been reached or the query to the consumer chain fails
for {
fp.logger.Debug(
"checking randomness",
zap.String("pk", fp.GetBtcPkHex()),
zap.Uint64("target_block_height", targetBlock.Height),
)
hasRand, err := fp.hasTimestampedRandomness(targetBlock.Height)
if err != nil {
fp.logger.Debug(
"failed to check last committed randomness",
zap.String("pk", fp.GetBtcPkHex()),
zap.Uint32("current_failures", numRetries),
zap.Uint64("target_block_height", targetBlock.Height),
zap.Error(err),
)

numRetries += 1
if numRetries > uint32(fp.cfg.MaxSubmissionRetries) {
return false, fmt.Errorf("reached max failed cycles with err: %w", err)
}
} else if !hasRand {
fp.logger.Debug(
"randomness does not exist",
zap.String("pk", fp.GetBtcPkHex()),
zap.Uint32("current_retries", numRetries),
zap.Uint64("target_block_height", targetBlock.Height),
)

numRetries += 1
if numRetries > uint32(fp.cfg.MaxSubmissionRetries) {
return false, fmt.Errorf("reached max retries but randomness still not existed")
}
} else {
// the randomness has been successfully committed
return false, nil
}
select {
case <-time.After(fp.cfg.SubmissionRetryInterval):
// periodically query the index block to be later checked whether it is Finalized
finalized, err := fp.checkBlockFinalization(targetBlock.Height)
if err != nil {
return false, fmt.Errorf("failed to query block finalization at height %v: %w", targetBlock.Height, err)
}
if finalized {
fp.logger.Debug(
"the block is already finalized, skip checking randomness",
zap.String("pk", fp.GetBtcPkHex()),
zap.Uint64("target_height", targetBlock.Height),
)
// TODO: returning nil here is to safely break the loop
// the error still exists
return true, nil
}

case <-fp.quit:
fp.logger.Debug("the finality-provider instance is closing", zap.String("pk", fp.GetBtcPkHex()))
return false, ErrFinalityProviderShutDown
}
}
}

// retrySubmitFinalitySignatureUntilBlockFinalized periodically tries to submit finality signature until success or the block is finalized
// error will be returned if maximum retries have been reached or the query to the consumer chain fails
func (fp *FinalityProviderInstance) retrySubmitFinalitySignatureUntilBlockFinalized(targetBlock *types.BlockInfo) (*types.TxResponse, error) {
Expand Down
1 change: 0 additions & 1 deletion finality-provider/service/fp_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ func FuzzStatusUpdate(f *testing.F) {

votingPower := uint64(r.Intn(2))
mockClientController.EXPECT().QueryFinalityProviderVotingPower(gomock.Any(), currentHeight).Return(votingPower, nil).AnyTimes()
mockClientController.EXPECT().QueryIsPubRandTimestamped(gomock.Any()).Return(true, nil).AnyTimes()
mockClientController.EXPECT().SubmitFinalitySig(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&types.TxResponse{TxHash: ""}, nil).AnyTimes()
var slashedHeight uint64
if votingPower == 0 {
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
cosmossdk.io/errors v1.0.1
cosmossdk.io/math v1.3.0
github.com/avast/retry-go/v4 v4.5.1
github.com/babylonlabs-io/babylon v0.9.1-0.20240813112945-18449905037d
github.com/babylonlabs-io/babylon v0.9.2-0.20240820103319-5a4d7ddae3a9
github.com/btcsuite/btcd v0.24.2
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/btcsuite/btcd/btcutil v1.1.5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX
github.com/aws/aws-sdk-go v1.44.312 h1:llrElfzeqG/YOLFFKjg1xNpZCFJ2xraIi3PqSuP+95k=
github.com/aws/aws-sdk-go v1.44.312/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/babylonlabs-io/babylon v0.9.1-0.20240813112945-18449905037d h1:JrnBxKgFOVAFsCaM5NJ985X/JGY+kxTlQXQWHgi7L8c=
github.com/babylonlabs-io/babylon v0.9.1-0.20240813112945-18449905037d/go.mod h1:9VUUAwVaalXiDdPZT65SPoawKWpp6ple6tBr8Vw0NI8=
github.com/babylonlabs-io/babylon v0.9.2-0.20240820103319-5a4d7ddae3a9 h1:ceFxMqqgP0dhZEvALMCJEh51DZva6m1gKhkeuDC+h9E=
github.com/babylonlabs-io/babylon v0.9.2-0.20240820103319-5a4d7ddae3a9/go.mod h1:9VUUAwVaalXiDdPZT65SPoawKWpp6ple6tBr8Vw0NI8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
Expand Down
8 changes: 4 additions & 4 deletions itest/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestFinalityProviderLifeCycle(t *testing.T) {
fpIns := fpInsList[0]

// check the public randomness is committed
tm.WaitForFpPubRandCommitted(t, fpIns)
tm.WaitForFpPubRandTimestamped(t, fpIns)

// send a BTC delegation
_ = tm.InsertBTCDelegation(t, []*btcec.PublicKey{fpIns.GetBtcPk()}, stakingTime, stakingAmount)
Expand Down Expand Up @@ -65,7 +65,7 @@ func TestDoubleSigning(t *testing.T) {
fpIns := fpInsList[0]

// check the public randomness is committed
tm.WaitForFpPubRandCommitted(t, fpIns)
tm.WaitForFpPubRandTimestamped(t, fpIns)

// send a BTC delegation
_ = tm.InsertBTCDelegation(t, []*btcec.PublicKey{fpIns.GetBtcPk()}, stakingTime, stakingAmount)
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestMultipleFinalityProviders(t *testing.T) {
go func(fpi *service.FinalityProviderInstance) {
defer tm.Wg.Done()
// check the public randomness is committed
tm.WaitForFpPubRandCommitted(t, fpi)
tm.WaitForFpPubRandTimestamped(t, fpi)
// send a BTC delegation
_ = tm.InsertBTCDelegation(t, []*btcec.PublicKey{fpi.GetBtcPk()}, stakingTime, stakingAmount)
}(fpIns)
Expand Down Expand Up @@ -161,7 +161,7 @@ func TestFastSync(t *testing.T) {
fpIns := fpInsList[0]

// check the public randomness is committed
tm.WaitForFpPubRandCommitted(t, fpIns)
tm.WaitForFpPubRandTimestamped(t, fpIns)

// send a BTC delegation
_ = tm.InsertBTCDelegation(t, []*btcec.PublicKey{fpIns.GetBtcPk()}, stakingTime, stakingAmount)
Expand Down
Loading

0 comments on commit 883a406

Please sign in to comment.