diff --git a/clientcontroller/opstackl2/consumer.go b/clientcontroller/opstackl2/consumer.go index da1253b0..3cf28c66 100644 --- a/clientcontroller/opstackl2/consumer.go +++ b/clientcontroller/opstackl2/consumer.go @@ -181,41 +181,13 @@ func (cc *OPStackL2ConsumerController) SubmitFinalitySig( proof []byte, sig *btcec.ModNScalar, ) (*types.TxResponse, error) { - cmtProof := cmtcrypto.Proof{} - if err := cmtProof.Unmarshal(proof); err != nil { - return nil, err - } - - msg := SubmitFinalitySignatureMsg{ - SubmitFinalitySignature: SubmitFinalitySignatureMsgParams{ - FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex(), - Height: block.Height, - PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRand).MustMarshal(), - Proof: ConvertProof(cmtProof), - BlockHash: block.Hash, - Signature: bbntypes.NewSchnorrEOTSSigFromModNScalar(sig).MustMarshal(), - }, - } - payload, err := json.Marshal(msg) - if err != nil { - return nil, err - } - execMsg := &wasmtypes.MsgExecuteContract{ - Sender: cc.CwClient.MustGetAddr(), - Contract: cc.Cfg.OPFinalityGadgetAddress, - Msg: payload, - } - - res, err := cc.ReliablySendMsg(execMsg, nil, nil) - if err != nil { - return nil, err - } - cc.logger.Debug( - "Successfully submitted finality signature", - zap.Uint64("height", block.Height), - zap.String("block_hash", hex.EncodeToString(block.Hash)), + return cc.SubmitBatchFinalitySigs( + fpPk, + []*types.BlockInfo{block}, + []*btcec.FieldVal{pubRand}, + [][]byte{proof}, + []*btcec.ModNScalar{sig}, ) - return &types.TxResponse{TxHash: res.TxHash}, nil } // SubmitBatchFinalitySigs submits a batch of finality signatures @@ -230,6 +202,7 @@ func (cc *OPStackL2ConsumerController) SubmitBatchFinalitySigs( return nil, fmt.Errorf("the number of blocks %v should match the number of finality signatures %v", len(blocks), len(sigs)) } msgs := make([]sdk.Msg, 0, len(blocks)) + fpPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex() for i, block := range blocks { cmtProof := cmtcrypto.Proof{} if err := cmtProof.Unmarshal(proofList[i]); err != nil { @@ -238,7 +211,7 @@ func (cc *OPStackL2ConsumerController) SubmitBatchFinalitySigs( msg := SubmitFinalitySignatureMsg{ SubmitFinalitySignature: SubmitFinalitySignatureMsgParams{ - FpPubkeyHex: bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex(), + FpPubkeyHex: fpPkHex, Height: block.Height, PubRand: bbntypes.NewSchnorrPubRandFromFieldVal(pubRandList[i]).MustMarshal(), Proof: ConvertProof(cmtProof), @@ -264,6 +237,7 @@ func (cc *OPStackL2ConsumerController) SubmitBatchFinalitySigs( } cc.logger.Debug( "Successfully submitted finality signatures in a batch", + zap.String("fp_pk_hex", fpPkHex), zap.Uint64("start_height", blocks[0].Height), zap.Uint64("end_height", blocks[len(blocks)-1].Height), ) diff --git a/itest/opstackl2/op_e2e_test.go b/itest/opstackl2/op_e2e_test.go index 4c07cd9b..1c1fbdd9 100644 --- a/itest/opstackl2/op_e2e_test.go +++ b/itest/opstackl2/op_e2e_test.go @@ -4,8 +4,14 @@ package e2etest_op import ( + "encoding/hex" + "encoding/json" + "math/rand" + "strings" "testing" + "time" + "github.com/babylonlabs-io/finality-provider/testutil" "github.com/stretchr/testify/require" ) @@ -42,3 +48,56 @@ func TestPubRandCommitment(t *testing.T) { consumerCfg := ctm.ConsumerFpApp.GetConfig() require.Equal(t, uint64(consumerCfg.NumPubRand), pubRand.EndHeight()) } + +// TestFinalitySigSubmission tests the consumer controller's function: +// - SubmitBatchFinalitySigs +func TestFinalitySigSubmission(t *testing.T) { + ctm := StartOpL2ConsumerManager(t) + defer ctm.Stop(t) + + // create and register Babylon FP and OP consumer FP + fps := ctm.setupBabylonAndConsumerFp(t) + + // send a BTC delegation and wait for activation + consumerFpPk := fps[1] + ctm.delegateBTCAndWaitForActivation(t, fps[0], consumerFpPk) + + // get the consumer FP instance + consumerFpInstance := ctm.getConsumerFpInstance(t, consumerFpPk) + + // commit pub rand with start height 1 + // this will call consumer controller's CommitPubRandList function + _, err := consumerFpInstance.CommitPubRand(1) + require.NoError(t, err) + + // mock batch of blocks with start height 1 and end height 3 + blocks := testutil.GenBlocks( + rand.New(rand.NewSource(time.Now().UnixNano())), + 1, + 3, + ) + + // submit finality signature + // this will call consumer controller's SubmitBatchFinalitySignatures function + _, err = consumerFpInstance.SubmitBatchFinalitySignatures(blocks) + require.NoError(t, err) + + // fill the query message with the block height and hash + queryMsg := map[string]interface{}{ + "block_voters": map[string]interface{}{ + "height": blocks[2].Height, + // it requires the block hash without the 0x prefix + "hash": strings.TrimPrefix(hex.EncodeToString(blocks[2].Hash), "0x"), + }, + } + + // query block_voters from finality gadget CW contract + queryResponse := ctm.queryCwContract(t, queryMsg) + var voters []string + err = json.Unmarshal(queryResponse.Data, &voters) + require.NoError(t, err) + + // check the voter, it should be the consumer FP public key + require.Equal(t, 1, len(voters)) + require.Equal(t, consumerFpPk.MarshalHex(), voters[0]) +} diff --git a/itest/opstackl2/op_test_manager.go b/itest/opstackl2/op_test_manager.go index 13b9d8cf..812dd076 100644 --- a/itest/opstackl2/op_test_manager.go +++ b/itest/opstackl2/op_test_manager.go @@ -54,13 +54,10 @@ type OpL2ConsumerTestManager struct { ConsumerEOTSClient *client.EOTSManagerGRpcClient } -// Config is the config of the OP finality gadget cw contract -// It will be removed by the final PR -type Config struct { - ConsumerId string `json:"consumer_id"` -} - -// - start Babylon node and wait for it starts +// StartOpL2ConsumerManager +// - starts Babylon node and wait for it starts +// - deploys finality gadget cw contract +// - creates and starts Babylon and consumer FPs without any FP instances func StartOpL2ConsumerManager(t *testing.T) *OpL2ConsumerTestManager { // Setup base dir and logger testDir, err := e2eutils.BaseDir("op-fp-e2e-test") @@ -371,6 +368,33 @@ func (ctm *OpL2ConsumerTestManager) delegateBTCAndWaitForActivation(t *testing.T ctm.WaitForNActiveDels(t, 1) } +func (ctm *OpL2ConsumerTestManager) queryCwContract( + t *testing.T, + queryMsg map[string]interface{}, +) *wasmtypes.QuerySmartContractStateResponse { + logger := ctm.ConsumerFpApp.Logger() + + // create cosmwasm client + cwConfig := ctm.OpConsumerController.Cfg.ToCosmwasmConfig() + cwClient, err := opcc.NewCwClient(&cwConfig, logger) + require.NoError(t, err) + + // marshal query message + queryMsgBytes, err := json.Marshal(queryMsg) + require.NoError(t, err) + + var queryResponse *wasmtypes.QuerySmartContractStateResponse + require.Eventually(t, func() bool { + queryResponse, err = cwClient.QuerySmartContractState( + ctm.OpConsumerController.Cfg.OPFinalityGadgetAddress, + string(queryMsgBytes), + ) + return err == nil + }, e2eutils.EventuallyWaitTimeOut, e2eutils.EventuallyPollTime) + + return queryResponse +} + func (ctm *OpL2ConsumerTestManager) Stop(t *testing.T) { t.Log("Stopping test manager") var err error