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

Feat: impl QueryFinalityProviderHasPower in consumer chain #44

Merged
87 changes: 86 additions & 1 deletion clientcontroller/opstackl2/consumer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@
"encoding/hex"
"encoding/json"
"fmt"
bbnclient "github.com/babylonlabs-io/babylon/client/client"
btcstakingtypes "github.com/babylonlabs-io/babylon/x/btcstaking/types"
sdkquerytypes "github.com/cosmos/cosmos-sdk/types/query"
"math"
"math/big"

sdkErr "cosmossdk.io/errors"
wasmdparams "github.com/CosmWasm/wasmd/app/params"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
bbnapp "github.com/babylonlabs-io/babylon/app"

Check failure on line 17 in clientcontroller/opstackl2/consumer.go

View workflow job for this annotation

GitHub Actions / lint_test / build

github.com/babylonlabs-io/[email protected]: invalid version: git ls-remote -q origin in /home/runner/go/pkg/mod/cache/vcs/17da197d642e6e74aad8aaf07b22f5aadb19e3813bbb5ec2fa7136f8a64469b1: exit status 128:

Check failure on line 17 in clientcontroller/opstackl2/consumer.go

View workflow job for this annotation

GitHub Actions / lint_test / unit-tests

github.com/babylonlabs-io/[email protected]: invalid version: git ls-remote -q origin in /home/runner/go/pkg/mod/cache/vcs/17da197d642e6e74aad8aaf07b22f5aadb19e3813bbb5ec2fa7136f8a64469b1: exit status 128:
bbntypes "github.com/babylonlabs-io/babylon/types"
fgclient "github.com/babylonlabs-io/finality-gadget/client"
"github.com/babylonlabs-io/finality-provider/clientcontroller/api"
Expand Down Expand Up @@ -41,6 +44,7 @@
Cfg *fpcfg.OPStackL2Config
CwClient *cwclient.Client
opl2Client *ethclient.Client
bbnClient *bbnclient.Client
logger *zap.Logger
}

Expand All @@ -66,10 +70,26 @@
return nil, fmt.Errorf("failed to create OPStack L2 client: %w", err)
}

bbnConfig := opl2Cfg.ToBBNConfig()
babylonConfig := fpcfg.BBNConfigToBabylonConfig(&bbnConfig)

if err := babylonConfig.Validate(); err != nil {
return nil, fmt.Errorf("invalid config for Babylon client: %w", err)
}

bc, err := bbnclient.New(
&babylonConfig,
logger,
)
if err != nil {
return nil, fmt.Errorf("failed to create Babylon client: %w", err)
}

return &OPStackL2ConsumerController{
opl2Cfg,
cwClient,
opl2Client,
bc,
logger,
}, nil
}
Expand Down Expand Up @@ -248,7 +268,38 @@
// Now we can simply hardcode the voting power to true
// TODO: see this issue https://github.com/babylonlabs-io/finality-provider/issues/390 for more details
func (cc *OPStackL2ConsumerController) QueryFinalityProviderHasPower(fpPk *btcec.PublicKey, blockHeight uint64) (bool, error) {
return true, nil
fpBtcPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex()
var nextKey []byte

btcStakingParams, err := cc.bbnClient.QueryClient.BTCStakingParams()
if err != nil {
return false, err
}
for {
resp, err := cc.bbnClient.QueryClient.FinalityProviderDelegations(fpBtcPkHex, &sdkquerytypes.PageRequest{Key: nextKey, Limit: 100})
if err != nil {
return false, err
}

for _, btcDels := range resp.BtcDelegatorDelegations {
for _, btcDel := range btcDels.Dels {
active, err := cc.isDelegationActive(btcStakingParams, btcDel)
if err != nil {
continue
}
if active {
return true, nil
}
}
}

if resp.Pagination == nil || resp.Pagination.NextKey == nil {
break
}
nextKey = resp.Pagination.NextKey
}

return false, nil
}

// QueryLatestFinalizedBlock returns the finalized L2 block from a RPC call
Expand All @@ -259,6 +310,11 @@
if err != nil {
return nil, err
}

if l2Block.Number.Uint64() == 0 {
return nil, nil
}

return &types.BlockInfo{
Height: l2Block.Number.Uint64(),
Hash: l2Block.Hash().Bytes(),
Expand Down Expand Up @@ -348,6 +404,10 @@
if err != nil {
return false, err
}

if l2Block == nil {
return false, nil
}
if height > l2Block.Height {
return false, nil
}
Expand Down Expand Up @@ -486,3 +546,28 @@
cc.opl2Client.Close()
return cc.CwClient.Stop()
}

func (cc *OPStackL2ConsumerController) isDelegationActive(
btcStakingParams *btcstakingtypes.QueryParamsResponse,
btcDel *btcstakingtypes.BTCDelegationResponse,
) (bool, error) {

covQuorum := btcStakingParams.GetParams().CovenantQuorum
ud := btcDel.UndelegationResponse

if len(ud.GetDelegatorUnbondingSigHex()) > 0 {
return false, nil
}

if uint32(len(btcDel.CovenantSigs)) < covQuorum {
return false, nil
}
if len(ud.CovenantUnbondingSigList) < int(covQuorum) {
return false, nil
}
if len(ud.CovenantSlashingSigs) < int(covQuorum) {
return false, nil
}

return true, nil
}
19 changes: 19 additions & 0 deletions finality-provider/config/opstackl2.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

cwcfg "github.com/babylonlabs-io/finality-provider/cosmwasmclient/config"

"github.com/cosmos/btcutil/bech32"
)

Expand Down Expand Up @@ -78,3 +79,21 @@ func (cfg *OPStackL2Config) ToCosmwasmConfig() cwcfg.CosmwasmConfig {
SubmitterAddress: "",
}
}

func (cfg *OPStackL2Config) ToBBNConfig() BBNConfig {
return BBNConfig{
Key: cfg.Key,
ChainID: cfg.ChainID,
RPCAddr: cfg.RPCAddr,
AccountPrefix: cfg.AccountPrefix,
KeyringBackend: cfg.KeyringBackend,
GasAdjustment: cfg.GasAdjustment,
GasPrices: cfg.GasPrices,
KeyDirectory: cfg.KeyDirectory,
Debug: cfg.Debug,
Timeout: cfg.Timeout,
BlockTimeout: cfg.BlockTimeout,
OutputFormat: cfg.OutputFormat,
SignModeStr: cfg.SignModeStr,
}
}
5 changes: 4 additions & 1 deletion finality-provider/service/fp_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,10 @@ func (fp *FinalityProviderInstance) tryFastSync(targetBlockHeight uint64) (*Fast
}

fp.logger.Debug("the finality-provider is entering fast sync",
zap.Uint64("start_height", startHeight), zap.Uint64("target_block_height", targetBlockHeight))
zap.String("pk", fp.GetBtcPkHex()),
zap.Uint64("start_height", startHeight),
zap.Uint64("target_block_height", targetBlockHeight),
)

return fp.FastSync(startHeight, targetBlockHeight)
}
Expand Down
2 changes: 1 addition & 1 deletion itest/opstackl2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ Then run the following command to start the e2e tests:
$ make test-e2e-op

# Filter specific test
$ make test-e2e-op Filter=TestFinalityGadget
$ make test-e2e-op FILTER=TestFinalityGadget
```
40 changes: 24 additions & 16 deletions itest/opstackl2/op_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import (
"github.com/stretchr/testify/require"
)

// tests the finality signature submission to the op-finality-gadget contract
func TestOpSubmitFinalitySignature(t *testing.T) {
// TestOpFpNoVotingPower tests that the FP has no voting power if it has no BTC delegation
func TestOpFpNoVotingPower(t *testing.T) {
ctm := StartOpL2ConsumerManager(t, 1)
defer ctm.Stop(t)

Expand All @@ -27,27 +27,28 @@ func TestOpSubmitFinalitySignature(t *testing.T) {

e2eutils.WaitForFpPubRandCommitted(t, fpInstance)
// query the first committed pub rand
opcc := ctm.getOpCCAtIndex(1)
opcc := ctm.getOpCCAtIndex(0)
committedPubRand, err := queryFirstPublicRandCommit(opcc, fpInstance.GetBtcPk())
require.NoError(t, err)
committedStartHeight := committedPubRand.StartHeight
t.Logf(log.Prefix("First committed pubrandList startHeight %d"), committedStartHeight)
testBlocks := ctm.WaitForNBlocksAndReturn(t, committedStartHeight, 1)
testBlock := testBlocks[0]

// wait for the fp sign
ctm.WaitForFpVoteReachHeight(t, fpInstance, testBlock.Height)
queryBlock := &fgtypes.Block{
BlockHeight: testBlock.Height,
BlockHash: hex.EncodeToString(testBlock.Hash),
BlockTimestamp: 12345, // doesn't matter b/c the BTC client is mocked
}

// note: QueryFinalityProviderHasPower is hardcode to return true so FPs can still submit finality sigs even if they
// don't have voting power. But the finality sigs will not be counted at tally time.
// no BTC delegation, so the FP has no voting power
hasPower, err := opcc.QueryFinalityProviderHasPower(fpInstance.GetBtcPk(), queryBlock.BlockHeight)
require.NoError(t, err)
require.Equal(t, false, hasPower)

_, err = ctm.FinalityGadget.QueryIsBlockBabylonFinalized(queryBlock)
require.ErrorIs(t, err, fgtypes.ErrBtcStakingNotActivated)
t.Logf(log.Prefix("Expected no voting power"))
t.Logf(log.Prefix("Expected BTC staking not activated"))
}

// This test has two test cases:
Expand All @@ -65,8 +66,9 @@ func TestOpMultipleFinalityProviders(t *testing.T) {
{e2eutils.StakingTime, e2eutils.StakingAmount},
})

// wait until the BTC staking is activated
l2BlockAfterActivation := ctm.waitForBTCStakingActivation(t)
// BTC delegations are activated after SetupFinalityProviders
l2BlockAfterActivation, err := ctm.getOpCCAtIndex(0).QueryLatestBlockHeight()
require.NoError(t, err)

// check both FPs have committed their first public randomness
// TODO: we might use go routine to do this in parallel
Expand Down Expand Up @@ -135,8 +137,9 @@ func TestFinalityStuckAndRecover(t *testing.T) {
})
fpInstance := fpList[0]

// wait until the BTC staking is activated
l2BlockAfterActivation := ctm.waitForBTCStakingActivation(t)
// BTC delegations are activated after SetupFinalityProviders
l2BlockAfterActivation, err := ctm.getOpCCAtIndex(0).QueryLatestBlockHeight()
require.NoError(t, err)

// wait for the first block to be finalized since BTC staking is activated
e2eutils.WaitForFpPubRandCommittedReachTargetHeight(t, fpInstance, l2BlockAfterActivation)
Expand All @@ -156,16 +159,20 @@ func TestFinalityStuckAndRecover(t *testing.T) {
t.Logf(log.Prefix("last voted height %d"), lastVotedHeight)
// wait until the block finalized
require.Eventually(t, func() bool {
latestFinalizedBlock, err := ctm.getOpCCAtIndex(1).QueryLatestFinalizedBlock()
latestFinalizedBlock, err := ctm.getOpCCAtIndex(0).QueryLatestFinalizedBlock()
require.NoError(t, err)
if latestFinalizedBlock == nil {
return false
}
stuckHeight := latestFinalizedBlock.Height
return lastVotedHeight == stuckHeight
}, e2eutils.EventuallyWaitTimeOut, e2eutils.EventuallyPollTime)

// check the finality gets stuck. wait for a while to make sure it is stuck
time.Sleep(5 * ctm.getL1BlockTime())
latestFinalizedBlock, err := ctm.getOpCCAtIndex(1).QueryLatestFinalizedBlock()
latestFinalizedBlock, err := ctm.getOpCCAtIndex(0).QueryLatestFinalizedBlock()
require.NoError(t, err)
require.NotNil(t, latestFinalizedBlock)
stuckHeight := latestFinalizedBlock.Height
require.Equal(t, lastVotedHeight, stuckHeight)
t.Logf(log.Prefix("OP chain block finalized head stuck at height %d"), stuckHeight)
Expand Down Expand Up @@ -205,8 +212,9 @@ func TestFinalityGadgetServer(t *testing.T) {
e2eutils.WaitForFpPubRandCommitted(t, fpList[i])
}

// wait until the BTC staking is activated
l2BlockAfterActivation := ctm.waitForBTCStakingActivation(t)
// BTC delegations are activated after SetupFinalityProviders
l2BlockAfterActivation, err := ctm.getOpCCAtIndex(0).QueryLatestBlockHeight()
require.NoError(t, err)

// both FP will sign the first block
targetBlockHeight := ctm.WaitForTargetBlockPubRand(t, fpList, l2BlockAfterActivation)
Expand Down
30 changes: 5 additions & 25 deletions itest/opstackl2/op_test_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func StartOpL2ConsumerManager(t *testing.T, numOfConsumerFPs uint8) *OpL2Consume
testDir,
bh,
opL2ConsumerConfig,
numOfConsumerFPs+1,
numOfConsumerFPs,
logger,
&shutdownInterceptor,
t,
Expand Down Expand Up @@ -765,7 +765,7 @@ func (ctm *OpL2ConsumerTestManager) SetupFinalityProviders(
for i := 0; i < n; i++ {
ctm.InsertBTCDelegation(
t,
[]*btcec.PublicKey{bbnFpPk.MustToBTCPK(), consumerFpPkList[0].MustToBTCPK()},
[]*btcec.PublicKey{bbnFpPk.MustToBTCPK(), consumerFpPkList[i].MustToBTCPK()},
stakingParams[i].stakingTime,
stakingParams[i].stakingAmount,
)
Expand Down Expand Up @@ -831,6 +831,9 @@ func (ctm *OpL2ConsumerTestManager) WaitForBlockFinalized(
t.Logf(log.Prefix("failed to query latest finalized block %s"), err.Error())
return false
}
if latestFinalizedBlock == nil {
return false
}
finalizedBlockHeight = latestFinalizedBlock.Height
return finalizedBlockHeight >= checkedHeight
}, e2eutils.EventuallyWaitTimeOut, 5*ctm.getL2BlockTime())
Expand Down Expand Up @@ -934,29 +937,6 @@ func queryFirstOrLastPublicRandCommit(
return resp, nil
}

func (ctm *OpL2ConsumerTestManager) waitForBTCStakingActivation(t *testing.T) uint64 {
var l2BlockAfterActivation uint64
require.Eventually(t, func() bool {
latestBlockHeight, err := ctm.getOpCCAtIndex(0).QueryLatestBlockHeight()
require.NoError(t, err)
latestBlock, err := ctm.getOpCCAtIndex(0).QueryEthBlock(latestBlockHeight)
require.NoError(t, err)
l2BlockAfterActivation = latestBlock.Number.Uint64()

activatedTimestamp, err := ctm.FinalityGadget.QueryBtcStakingActivatedTimestamp()
if err != nil {
t.Logf(log.Prefix("Failed to query BTC staking activated timestamp: %v"), err)
return false
}
t.Logf(log.Prefix("Activated timestamp %d"), activatedTimestamp)

return latestBlock.Time >= activatedTimestamp
}, 30*ctm.getL2BlockTime(), ctm.getL2BlockTime())

t.Logf(log.Prefix("found a L2 block after BTC staking activation: %d"), l2BlockAfterActivation)
return l2BlockAfterActivation
}

func (ctm *OpL2ConsumerTestManager) Stop(t *testing.T) {
t.Log("Stopping test manager")
var err error
Expand Down
Loading